// Copyright 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/trees/layer_tree_host_impl.h"

#include <stddef.h>

#include <algorithm>
#include <cmath>
#include <utility>

#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "cc/animation/animation_host.h"
#include "cc/animation/animation_id_provider.h"
#include "cc/animation/transform_operations.h"
#include "cc/base/math_util.h"
#include "cc/input/browser_controls_offset_manager.h"
#include "cc/input/main_thread_scrolling_reason.h"
#include "cc/input/page_scale_animation.h"
#include "cc/input/scrollbar_animation_controller_thinning.h"
#include "cc/layers/append_quads_data.h"
#include "cc/layers/heads_up_display_layer_impl.h"
#include "cc/layers/layer_impl.h"
#include "cc/layers/painted_scrollbar_layer_impl.h"
#include "cc/layers/render_surface_impl.h"
#include "cc/layers/solid_color_layer_impl.h"
#include "cc/layers/solid_color_scrollbar_layer_impl.h"
#include "cc/layers/texture_layer_impl.h"
#include "cc/layers/video_layer_impl.h"
#include "cc/layers/viewport.h"
#include "cc/output/begin_frame_args.h"
#include "cc/output/compositor_frame_metadata.h"
#include "cc/output/copy_output_request.h"
#include "cc/output/copy_output_result.h"
#include "cc/output/gl_renderer.h"
#include "cc/output/latency_info_swap_promise.h"
#include "cc/quads/render_pass_draw_quad.h"
#include "cc/quads/solid_color_draw_quad.h"
#include "cc/quads/texture_draw_quad.h"
#include "cc/quads/tile_draw_quad.h"
#include "cc/resources/ui_resource_bitmap.h"
#include "cc/resources/ui_resource_manager.h"
#include "cc/test/animation_test_common.h"
#include "cc/test/begin_frame_args_test.h"
#include "cc/test/fake_compositor_frame_sink.h"
#include "cc/test/fake_layer_tree_host_impl.h"
#include "cc/test/fake_mask_layer_impl.h"
#include "cc/test/fake_output_surface.h"
#include "cc/test/fake_picture_layer_impl.h"
#include "cc/test/fake_raster_source.h"
#include "cc/test/fake_video_frame_provider.h"
#include "cc/test/geometry_test_utils.h"
#include "cc/test/gpu_rasterization_enabled_settings.h"
#include "cc/test/layer_test_common.h"
#include "cc/test/layer_tree_test.h"
#include "cc/test/test_compositor_frame_sink.h"
#include "cc/test/test_task_graph_runner.h"
#include "cc/test/test_web_graphics_context_3d.h"
#include "cc/trees/effect_node.h"
#include "cc/trees/layer_tree_host_common.h"
#include "cc/trees/layer_tree_impl.h"
#include "cc/trees/mutator_host.h"
#include "cc/trees/single_thread_proxy.h"
#include "cc/trees/transform_node.h"
#include "media/base/media.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkMallocPixelRef.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/geometry/vector2d_conversions.h"

#define EXPECT_SCOPED(statements) \
    {                             \
        SCOPED_TRACE("");         \
        statements;               \
    }

using media::VideoFrame;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::AtLeast;
using ::testing::Mock;
using ::testing::Return;

namespace cc {
namespace {

    class LayerTreeHostImplTest : public testing::Test,
                                  public LayerTreeHostImplClient {
    public:
        LayerTreeHostImplTest()
            : task_runner_provider_(base::ThreadTaskRunnerHandle::Get())
            , always_main_thread_blocked_(&task_runner_provider_)
            , on_can_draw_state_changed_called_(false)
            , did_notify_ready_to_activate_(false)
            , did_request_commit_(false)
            , did_request_redraw_(false)
            , did_request_next_frame_(false)
            , did_request_prepare_tiles_(false)
            , did_complete_page_scale_animation_(false)
            , reduce_memory_result_(true)
        {
            media::InitializeMediaLibrary();
        }

        LayerTreeSettings DefaultSettings()
        {
            LayerTreeSettings settings;
            settings.minimum_occlusion_tracking_size = gfx::Size();
            settings.renderer_settings.texture_id_allocation_chunk_size = 1;
            settings.gpu_rasterization_enabled = true;
            settings.verify_clip_tree_calculations = true;
            settings.renderer_settings.buffer_to_texture_target_map = DefaultBufferToTextureTargetMapForTesting();
            return settings;
        }

        void SetUp() override
        {
            CreateHostImpl(DefaultSettings(), CreateCompositorFrameSink());
        }

        void TearDown() override
        {
            if (host_impl_)
                host_impl_->ReleaseCompositorFrameSink();
        }

        void DidLoseCompositorFrameSinkOnImplThread() override { }
        void SetBeginFrameSource(BeginFrameSource* source) override { }
        void DidReceiveCompositorFrameAckOnImplThread() override { }
        void OnCanDrawStateChanged(bool can_draw) override
        {
            on_can_draw_state_changed_called_ = true;
        }
        void NotifyReadyToActivate() override
        {
            did_notify_ready_to_activate_ = true;
            host_impl_->ActivateSyncTree();
        }
        void NotifyReadyToDraw() override { }
        void SetNeedsRedrawOnImplThread() override { did_request_redraw_ = true; }
        void SetNeedsOneBeginImplFrameOnImplThread() override
        {
            did_request_next_frame_ = true;
        }
        void SetNeedsPrepareTilesOnImplThread() override
        {
            did_request_prepare_tiles_ = true;
        }
        void SetNeedsCommitOnImplThread() override { did_request_commit_ = true; }
        void SetVideoNeedsBeginFrames(bool needs_begin_frames) override { }
        void PostAnimationEventsToMainThreadOnImplThread(
            std::unique_ptr<MutatorEvents> events) override { }
        bool IsInsideDraw() override { return false; }
        void RenewTreePriority() override { }
        void PostDelayedAnimationTaskOnImplThread(const base::Closure& task,
            base::TimeDelta delay) override
        {
            animation_task_ = task;
            requested_animation_delay_ = delay;
        }
        void DidActivateSyncTree() override { }
        void WillPrepareTiles() override { }
        void DidPrepareTiles() override { }
        void DidCompletePageScaleAnimationOnImplThread() override
        {
            did_complete_page_scale_animation_ = true;
        }
        void OnDrawForCompositorFrameSink(bool resourceless_software_draw) override
        {
            std::unique_ptr<LayerTreeHostImpl::FrameData> frame(
                new LayerTreeHostImpl::FrameData);
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(frame.get()));
            last_on_draw_render_passes_.clear();
            RenderPass::CopyAll(frame->render_passes, &last_on_draw_render_passes_);
            host_impl_->DrawLayers(frame.get());
            host_impl_->DidDrawAllLayers(*frame);
            last_on_draw_frame_ = std::move(frame);
        }

        void set_reduce_memory_result(bool reduce_memory_result)
        {
            reduce_memory_result_ = reduce_memory_result;
        }

        virtual bool CreateHostImpl(
            const LayerTreeSettings& settings,
            std::unique_ptr<CompositorFrameSink> compositor_frame_sink)
        {
            return CreateHostImplWithTaskRunnerProvider(
                settings, std::move(compositor_frame_sink), &task_runner_provider_);
        }

        AnimationHost* GetImplAnimationHost() const
        {
            return static_cast<AnimationHost*>(host_impl_->mutator_host());
        }

        virtual bool CreateHostImplWithTaskRunnerProvider(
            const LayerTreeSettings& settings,
            std::unique_ptr<CompositorFrameSink> compositor_frame_sink,
            TaskRunnerProvider* task_runner_provider)
        {
            if (host_impl_)
                host_impl_->ReleaseCompositorFrameSink();
            host_impl_ = LayerTreeHostImpl::Create(
                settings, this, task_runner_provider, &stats_instrumentation_,
                &task_graph_runner_,
                AnimationHost::CreateForTesting(ThreadInstance::IMPL), 0, nullptr);
            compositor_frame_sink_ = std::move(compositor_frame_sink);
            host_impl_->SetVisible(true);
            bool init = host_impl_->InitializeRenderer(compositor_frame_sink_.get());
            host_impl_->SetViewportSize(gfx::Size(10, 10));
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
            // Set the BeginFrameArgs so that methods which use it are able to.
            host_impl_->WillBeginImplFrame(CreateBeginFrameArgsForTesting(
                BEGINFRAME_FROM_HERE, 0, 1,
                base::TimeTicks() + base::TimeDelta::FromMilliseconds(1)));
            host_impl_->DidFinishImplFrame();

            timeline_ = AnimationTimeline::Create(AnimationIdProvider::NextTimelineId());
            GetImplAnimationHost()->AddAnimationTimeline(timeline_);

            return init;
        }

        void SetupRootLayerImpl(std::unique_ptr<LayerImpl> root)
        {
            root->SetPosition(gfx::PointF());
            root->SetBounds(gfx::Size(10, 10));
            root->SetDrawsContent(true);
            root->draw_properties().visible_layer_rect = gfx::Rect(0, 0, 10, 10);
            root->test_properties()->force_render_surface = true;
            host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        }

        static gfx::Vector2dF ScrollDelta(LayerImpl* layer_impl)
        {
            gfx::ScrollOffset delta = layer_impl->layer_tree_impl()
                                          ->property_trees()
                                          ->scroll_tree.GetScrollOffsetDeltaForTesting(layer_impl->id());
            return gfx::Vector2dF(delta.x(), delta.y());
        }

        static void ExpectClearedScrollDeltasRecursive(LayerImpl* root)
        {
            for (auto* layer : *root->layer_tree_impl())
                ASSERT_EQ(ScrollDelta(layer), gfx::Vector2d());
        }

        static ::testing::AssertionResult ScrollInfoContains(
            const ScrollAndScaleSet& scroll_info,
            int id,
            const gfx::Vector2d& scroll_delta)
        {
            int times_encountered = 0;

            for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
                if (scroll_info.scrolls[i].layer_id != id)
                    continue;

                if (scroll_delta != scroll_info.scrolls[i].scroll_delta) {
                    return ::testing::AssertionFailure()
                        << "Expected " << scroll_delta.ToString() << ", not "
                        << scroll_info.scrolls[i].scroll_delta.ToString();
                }
                times_encountered++;
            }

            if (id == scroll_info.inner_viewport_scroll.layer_id) {
                if (scroll_delta != scroll_info.inner_viewport_scroll.scroll_delta) {
                    return ::testing::AssertionFailure()
                        << "Expected " << scroll_delta.ToString() << ", not "
                        << scroll_info.inner_viewport_scroll.scroll_delta.ToString();
                }
                times_encountered++;
            }

            if (times_encountered != 1)
                return ::testing::AssertionFailure() << "No layer found with id " << id;
            return ::testing::AssertionSuccess();
        }

        static void ExpectNone(const ScrollAndScaleSet& scroll_info, int id)
        {
            int times_encountered = 0;

            for (size_t i = 0; i < scroll_info.scrolls.size(); ++i) {
                if (scroll_info.scrolls[i].layer_id != id)
                    continue;
                times_encountered++;
            }

            ASSERT_EQ(0, times_encountered);
        }

        LayerImpl* CreateScrollAndContentsLayers(LayerTreeImpl* layer_tree_impl,
            const gfx::Size& content_size)
        {
            // Create both an inner viewport scroll layer and an outer viewport scroll
            // layer. The MaxScrollOffset of the outer viewport scroll layer will be
            // 0x0, so the scrolls will be applied directly to the inner viewport.
            const int kOuterViewportClipLayerId = 116;
            const int kOuterViewportScrollLayerId = 117;
            const int kContentLayerId = 118;
            const int kInnerViewportScrollLayerId = 2;
            const int kInnerViewportClipLayerId = 4;
            const int kPageScaleLayerId = 5;

            std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
            root->SetBounds(content_size);
            root->SetPosition(gfx::PointF());
            root->test_properties()->force_render_surface = true;

            std::unique_ptr<LayerImpl> inner_scroll = LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId);
            inner_scroll->test_properties()->is_container_for_fixed_position_layers = true;
            inner_scroll->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(inner_scroll->id(),
                    gfx::ScrollOffset());

            std::unique_ptr<LayerImpl> inner_clip = LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId);
            inner_clip->SetBounds(
                gfx::Size(content_size.width() / 2, content_size.height() / 2));

            std::unique_ptr<LayerImpl> page_scale = LayerImpl::Create(layer_tree_impl, kPageScaleLayerId);

            inner_scroll->SetScrollClipLayer(inner_clip->id());
            inner_scroll->SetBounds(content_size);
            inner_scroll->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> outer_clip = LayerImpl::Create(layer_tree_impl, kOuterViewportClipLayerId);
            outer_clip->SetBounds(content_size);
            outer_clip->test_properties()->is_container_for_fixed_position_layers = true;

            std::unique_ptr<LayerImpl> outer_scroll = LayerImpl::Create(layer_tree_impl, kOuterViewportScrollLayerId);
            outer_scroll->SetScrollClipLayer(outer_clip->id());
            outer_scroll->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(outer_scroll->id(),
                    gfx::ScrollOffset());
            outer_scroll->SetBounds(content_size);
            outer_scroll->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> contents = LayerImpl::Create(layer_tree_impl, kContentLayerId);
            contents->SetDrawsContent(true);
            contents->SetBounds(content_size);
            contents->SetPosition(gfx::PointF());

            outer_scroll->test_properties()->AddChild(std::move(contents));
            outer_clip->test_properties()->AddChild(std::move(outer_scroll));
            inner_scroll->test_properties()->AddChild(std::move(outer_clip));
            page_scale->test_properties()->AddChild(std::move(inner_scroll));
            inner_clip->test_properties()->AddChild(std::move(page_scale));
            root->test_properties()->AddChild(std::move(inner_clip));

            layer_tree_impl->SetRootLayerForTesting(std::move(root));
            layer_tree_impl->BuildPropertyTreesForTesting();
            layer_tree_impl->SetViewportLayersFromIds(
                Layer::INVALID_ID, kPageScaleLayerId, kInnerViewportScrollLayerId,
                kOuterViewportScrollLayerId);

            layer_tree_impl->DidBecomeActive();
            return layer_tree_impl->InnerViewportScrollLayer();
        }

        LayerImpl* SetupScrollAndContentsLayers(const gfx::Size& content_size)
        {
            LayerImpl* scroll_layer = CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
            host_impl_->active_tree()->DidBecomeActive();
            return scroll_layer;
        }

        // Sets up a typical virtual viewport setup with one child content layer.
        // Returns a pointer to the content layer.
        LayerImpl* CreateBasicVirtualViewportLayers(const gfx::Size& viewport_size,
            const gfx::Size& content_size)
        {
            // CreateScrollAndContentsLayers makes the outer viewport unscrollable and
            // the inner a different size from the outer. We'll reuse its layer
            // hierarchy but adjust the sizing to our needs.
            CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);

            LayerImpl* content_layer = host_impl_->OuterViewportScrollLayer()
                                           ->test_properties()
                                           ->children.back();
            content_layer->SetBounds(content_size);
            host_impl_->OuterViewportScrollLayer()->SetBounds(content_size);

            LayerImpl* outer_clip = host_impl_->OuterViewportScrollLayer()->test_properties()->parent;
            outer_clip->SetBounds(viewport_size);

            LayerImpl* inner_clip_layer = host_impl_->InnerViewportScrollLayer()
                                              ->test_properties()
                                              ->parent->test_properties()
                                              ->parent;
            inner_clip_layer->SetBounds(viewport_size);
            host_impl_->InnerViewportScrollLayer()->SetBounds(viewport_size);

            // Needs to happen before building property trees as the latter propagates
            // these element ids to property tree nodes.
            host_impl_->active_tree()->SetElementIdsForTesting();
            host_impl_->active_tree()->BuildPropertyTreesForTesting();

            host_impl_->SetViewportSize(viewport_size);
            host_impl_->active_tree()->DidBecomeActive();

            return content_layer;
        }

        std::unique_ptr<LayerImpl> CreateScrollableLayer(int id,
            const gfx::Size& size,
            LayerImpl* clip_layer)
        {
            DCHECK(clip_layer);
            DCHECK(id != clip_layer->id());
            std::unique_ptr<LayerImpl> layer = LayerImpl::Create(host_impl_->active_tree(), id);
            layer->SetScrollClipLayer(clip_layer->id());
            layer->SetDrawsContent(true);
            layer->SetBounds(size);
            clip_layer->SetBounds(gfx::Size(size.width() / 2, size.height() / 2));
            return layer;
        }

        std::unique_ptr<ScrollState> BeginState(const gfx::Point& point)
        {
            ScrollStateData scroll_state_data;
            scroll_state_data.is_beginning = true;
            scroll_state_data.position_x = point.x();
            scroll_state_data.position_y = point.y();
            std::unique_ptr<ScrollState> scroll_state(
                new ScrollState(scroll_state_data));
            return scroll_state;
        }

        std::unique_ptr<ScrollState> UpdateState(const gfx::Point& point,
            const gfx::Vector2dF& delta)
        {
            ScrollStateData scroll_state_data;
            scroll_state_data.delta_x = delta.x();
            scroll_state_data.delta_y = delta.y();
            scroll_state_data.position_x = point.x();
            scroll_state_data.position_y = point.y();
            std::unique_ptr<ScrollState> scroll_state(
                new ScrollState(scroll_state_data));
            return scroll_state;
        }

        std::unique_ptr<ScrollState> EndState()
        {
            ScrollStateData scroll_state_data;
            scroll_state_data.is_ending = true;
            std::unique_ptr<ScrollState> scroll_state(
                new ScrollState(scroll_state_data));
            return scroll_state;
        }

        void DrawFrame()
        {
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            host_impl_->DrawLayers(&frame);
            host_impl_->DidDrawAllLayers(frame);
        }

        void pinch_zoom_pan_viewport_forces_commit_redraw(float device_scale_factor);
        void pinch_zoom_pan_viewport_test(float device_scale_factor);
        void pinch_zoom_pan_viewport_and_scroll_test(float device_scale_factor);
        void pinch_zoom_pan_viewport_and_scroll_boundary_test(
            float device_scale_factor);

        void SetupMouseMoveAtWithDeviceScale(float device_scale_factor);

        void SetupMouseMoveAtTestScrollbarStates(bool main_thread_scrolling);

        scoped_refptr<AnimationTimeline> timeline() { return timeline_; }

    protected:
        virtual std::unique_ptr<CompositorFrameSink> CreateCompositorFrameSink()
        {
            return FakeCompositorFrameSink::Create3d();
        }

        void DrawOneFrame()
        {
            LayerTreeHostImpl::FrameData frame_data;
            host_impl_->PrepareToDraw(&frame_data);
            host_impl_->DidDrawAllLayers(frame_data);
        }

        static void SetScrollOffsetDelta(LayerImpl* layer_impl,
            const gfx::Vector2dF& delta)
        {
            if (layer_impl->layer_tree_impl()
                    ->property_trees()
                    ->scroll_tree.SetScrollOffsetDeltaForTesting(layer_impl->id(),
                        delta))
                layer_impl->layer_tree_impl()->DidUpdateScrollOffset(layer_impl->id());
        }

        void BeginImplFrameAndAnimate(BeginFrameArgs begin_frame_args,
            base::TimeTicks frame_time)
        {
            begin_frame_args.frame_time = frame_time;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            host_impl_->UpdateAnimationState(true);
            host_impl_->DidFinishImplFrame();
        }

        FakeImplTaskRunnerProvider task_runner_provider_;
        DebugScopedSetMainThreadBlocked always_main_thread_blocked_;

        TestTaskGraphRunner task_graph_runner_;
        std::unique_ptr<CompositorFrameSink> compositor_frame_sink_;
        std::unique_ptr<LayerTreeHostImpl> host_impl_;
        FakeRenderingStatsInstrumentation stats_instrumentation_;
        bool on_can_draw_state_changed_called_;
        bool did_notify_ready_to_activate_;
        bool did_request_commit_;
        bool did_request_redraw_;
        bool did_request_next_frame_;
        bool did_request_prepare_tiles_;
        bool did_complete_page_scale_animation_;
        bool reduce_memory_result_;
        base::Closure animation_task_;
        base::TimeDelta requested_animation_delay_;
        std::unique_ptr<LayerTreeHostImpl::FrameData> last_on_draw_frame_;
        RenderPassList last_on_draw_render_passes_;
        scoped_refptr<AnimationTimeline> timeline_;
    };

    // A test fixture for new animation timelines tests.
    class LayerTreeHostImplTimelinesTest : public LayerTreeHostImplTest {
    public:
        void SetUp() override
        {
            CreateHostImpl(DefaultSettings(), CreateCompositorFrameSink());
        }
    };

    TEST_F(LayerTreeHostImplTest, NotifyIfCanDrawChanged)
    {
        // Note: It is not possible to disable the renderer once it has been set,
        // so we do not need to test that disabling the renderer notifies us
        // that can_draw changed.
        EXPECT_FALSE(host_impl_->CanDraw());
        on_can_draw_state_changed_called_ = false;

        // Set up the root layer, which allows us to draw.
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        EXPECT_TRUE(host_impl_->CanDraw());
        EXPECT_TRUE(on_can_draw_state_changed_called_);
        on_can_draw_state_changed_called_ = false;

        // Toggle the root layer to make sure it toggles can_draw
        host_impl_->active_tree()->SetRootLayerForTesting(nullptr);
        host_impl_->active_tree()->DetachLayers();
        EXPECT_FALSE(host_impl_->CanDraw());
        EXPECT_TRUE(on_can_draw_state_changed_called_);
        on_can_draw_state_changed_called_ = false;

        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        EXPECT_TRUE(host_impl_->CanDraw());
        EXPECT_TRUE(on_can_draw_state_changed_called_);
        on_can_draw_state_changed_called_ = false;

        // Toggle the device viewport size to make sure it toggles can_draw.
        host_impl_->SetViewportSize(gfx::Size());
        EXPECT_FALSE(host_impl_->CanDraw());
        EXPECT_TRUE(on_can_draw_state_changed_called_);
        on_can_draw_state_changed_called_ = false;

        host_impl_->SetViewportSize(gfx::Size(100, 100));
        EXPECT_TRUE(host_impl_->CanDraw());
        EXPECT_TRUE(on_can_draw_state_changed_called_);
        on_can_draw_state_changed_called_ = false;
    }

    TEST_F(LayerTreeHostImplTest, ResourcelessDrawWithEmptyViewport)
    {
        CreateHostImpl(DefaultSettings(), FakeCompositorFrameSink::CreateSoftware());
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        EXPECT_TRUE(host_impl_->CanDraw());
        host_impl_->SetViewportSize(gfx::Size());
        EXPECT_FALSE(host_impl_->CanDraw());

        FakeCompositorFrameSink* fake_compositor_frame_sink = static_cast<FakeCompositorFrameSink*>(
            host_impl_->compositor_frame_sink());
        EXPECT_EQ(fake_compositor_frame_sink->num_sent_frames(), 0u);
        gfx::Transform identity;
        gfx::Rect viewport(100, 100);
        const bool resourceless_software_draw = true;
        host_impl_->OnDraw(identity, viewport, resourceless_software_draw);
        ASSERT_EQ(fake_compositor_frame_sink->num_sent_frames(), 1u);
        EXPECT_EQ(
            gfx::SizeF(100.f, 100.f),
            fake_compositor_frame_sink->last_sent_frame()->metadata.root_layer_size);
    }

    TEST_F(LayerTreeHostImplTest, ScrollDeltaNoLayers)
    {
        ASSERT_FALSE(host_impl_->active_tree()->root_layer_for_testing());

        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        ASSERT_EQ(scroll_info->scrolls.size(), 0u);
    }

    TEST_F(LayerTreeHostImplTest, ScrollDeltaTreeButNoChanges)
    {
        {
            std::unique_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
            root->test_properties()->AddChild(
                LayerImpl::Create(host_impl_->active_tree(), 2));
            root->test_properties()->AddChild(
                LayerImpl::Create(host_impl_->active_tree(), 3));
            root->test_properties()->children[1]->test_properties()->AddChild(
                LayerImpl::Create(host_impl_->active_tree(), 4));
            root->test_properties()->children[1]->test_properties()->AddChild(
                LayerImpl::Create(host_impl_->active_tree(), 5));
            root->test_properties()
                ->children[1]
                ->test_properties()
                ->children[0]
                ->test_properties()
                ->AddChild(LayerImpl::Create(host_impl_->active_tree(), 6));
            host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        }
        LayerImpl* root = *host_impl_->active_tree()->begin();

        ExpectClearedScrollDeltasRecursive(root);

        std::unique_ptr<ScrollAndScaleSet> scroll_info;

        scroll_info = host_impl_->ProcessScrollDeltas();
        ASSERT_EQ(scroll_info->scrolls.size(), 0u);
        ExpectClearedScrollDeltasRecursive(root);

        scroll_info = host_impl_->ProcessScrollDeltas();
        ASSERT_EQ(scroll_info->scrolls.size(), 0u);
        ExpectClearedScrollDeltasRecursive(root);
    }

    TEST_F(LayerTreeHostImplTest, ScrollDeltaRepeatedScrolls)
    {
        gfx::ScrollOffset scroll_offset(20, 30);
        gfx::Vector2d scroll_delta(11, -15);

        auto root_clip_owned = LayerImpl::Create(host_impl_->active_tree(), 2);
        auto* root_clip = root_clip_owned.get();
        auto root_owned = LayerImpl::Create(host_impl_->active_tree(), 1);
        auto* root = root_owned.get();

        root_clip->SetBounds(gfx::Size(10, 10));
        root_clip->test_properties()->AddChild(std::move(root_owned));
        root->SetBounds(gfx::Size(110, 110));
        root->SetScrollClipLayer(root_clip->id());
        root->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(root->id(), scroll_offset);
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_clip_owned));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        std::unique_ptr<ScrollAndScaleSet> scroll_info;

        root->ScrollBy(scroll_delta);
        scroll_info = host_impl_->ProcessScrollDeltas();
        ASSERT_EQ(scroll_info->scrolls.size(), 1u);
        EXPECT_TRUE(ScrollInfoContains(*scroll_info, root->id(), scroll_delta));

        gfx::Vector2d scroll_delta2(-5, 27);
        root->ScrollBy(scroll_delta2);
        scroll_info = host_impl_->ProcessScrollDeltas();
        ASSERT_EQ(scroll_info->scrolls.size(), 1u);
        EXPECT_TRUE(ScrollInfoContains(*scroll_info, root->id(),
            scroll_delta + scroll_delta2));

        root->ScrollBy(gfx::Vector2d());
        scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info, root->id(),
            scroll_delta + scroll_delta2));
    }

    TEST_F(LayerTreeHostImplTest, ScrollRootCallsCommitAndRedraw)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
            InputHandler::WHEEL));
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(0, 10),
            InputHandler::WHEEL));
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
            InputHandler::WHEEL));
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_TRUE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, ScrollActiveOnlyAfterScrollMovement)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        EXPECT_FALSE(host_impl_->IsActivelyScrolling());
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        EXPECT_TRUE(host_impl_->IsActivelyScrolling());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FALSE(host_impl_->IsActivelyScrolling());
    }

    TEST_F(LayerTreeHostImplTest, ScrollWithoutRootLayer)
    {
        // We should not crash when trying to scroll an empty layer tree.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ScrollWithoutRenderer)
    {
        std::unique_ptr<TestWebGraphicsContext3D> context_owned = TestWebGraphicsContext3D::Create();
        context_owned->set_context_lost(true);

        // Initialization will fail.
        EXPECT_FALSE(CreateHostImpl(
            DefaultSettings(),
            FakeCompositorFrameSink::Create3d(std::move(context_owned))));

        SetupScrollAndContentsLayers(gfx::Size(100, 100));

        // We should not crash when trying to scroll after the renderer initialization
        // fails.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ReplaceTreeWhileScrolling)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        // We should not crash if the tree is replaced while we are scrolling.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->active_tree()->DetachLayers();

        scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));

        // We should still be scrolling, because the scrolled layer also exists in the
        // new tree.
        gfx::Vector2d scroll_delta(0, 10);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());
        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(
            ScrollInfoContains(*scroll_info, scroll_layer->id(), scroll_delta));
    }

    TEST_F(LayerTreeHostImplTest, ScrollBlocksOnWheelEventHandlers)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        // Wheel handlers determine whether mouse events block scroll.
        host_impl_->active_tree()->set_event_listener_properties(
            EventListenerClass::kMouseWheel, EventListenerProperties::kBlocking);
        EXPECT_EQ(
            EventListenerProperties::kBlocking,
            host_impl_->GetEventListenerProperties(EventListenerClass::kMouseWheel));

        // But they don't influence the actual handling of the scroll gestures.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);
        host_impl_->ScrollEnd(EndState().get());
    }

    TEST_F(LayerTreeHostImplTest, ScrollBlocksOnTouchEventHandlers)
    {
        LayerImpl* scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();
        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();

        LayerImpl* child = 0;
        {
            std::unique_ptr<LayerImpl> child_layer = LayerImpl::Create(host_impl_->active_tree(), 6);
            child = child_layer.get();
            child_layer->SetDrawsContent(true);
            child_layer->SetPosition(gfx::PointF(0, 20));
            child_layer->SetBounds(gfx::Size(50, 50));
            scroll->test_properties()->AddChild(std::move(child_layer));
            host_impl_->active_tree()->BuildPropertyTreesForTesting();
        }

        // Touch handler regions determine whether touch events block scroll.
        root->SetTouchEventHandlerRegion(gfx::Rect(0, 0, 100, 100));
        EXPECT_EQ(InputHandler::TouchStartEventListenerType::HANDLER,
            host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(10, 10)));

        // But they don't influence the actual handling of the scroll gestures.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_EQ(InputHandler::TouchStartEventListenerType::HANDLER,
            host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(10, 30)));
        root->SetTouchEventHandlerRegion(gfx::Rect());
        EXPECT_EQ(InputHandler::TouchStartEventListenerType::NO_HANDLER,
            host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(10, 30)));
        child->SetTouchEventHandlerRegion(gfx::Rect(0, 0, 50, 50));
        EXPECT_EQ(InputHandler::TouchStartEventListenerType::HANDLER,
            host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(10, 30)));
    }

    TEST_F(LayerTreeHostImplTest, FlingOnlyWhenScrollingTouchscreen)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        // Ignore the fling since no layer is being scrolled
        InputHandler::ScrollStatus status = host_impl_->FlingScrollBegin();
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);

        // Start scrolling a layer
        status = host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        // Now the fling should go ahead since we've started scrolling a layer
        status = host_impl_->FlingScrollBegin();
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, FlingOnlyWhenScrollingTouchpad)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        // Ignore the fling since no layer is being scrolled
        InputHandler::ScrollStatus status = host_impl_->FlingScrollBegin();
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);

        // Start scrolling a layer
        status = host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        // Now the fling should go ahead since we've started scrolling a layer
        status = host_impl_->FlingScrollBegin();
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, NoFlingWhenScrollingOnMain)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();

        root->set_main_thread_scrolling_reasons(
            MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        // Start scrolling a layer
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
            status.main_thread_scrolling_reasons);

        // The fling should be ignored since there's no layer being scrolled impl-side
        status = host_impl_->FlingScrollBegin();
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ShouldScrollOnMainThread)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();

        root->set_main_thread_scrolling_reasons(
            MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        DrawFrame();

        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
            status.main_thread_scrolling_reasons);

        status = host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ScrollWithOverlappingNonScrollableLayer)
    {
        LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
        gfx::Size content_size = gfx::Size(360, 600);
        gfx::Size scroll_content_size = gfx::Size(345, 3800);
        gfx::Size scrollbar_size = gfx::Size(15, 600);

        host_impl_->SetViewportSize(content_size);
        std::unique_ptr<LayerImpl> root = LayerImpl::Create(layer_tree_impl, 1);
        root->SetBounds(content_size);
        root->SetPosition(gfx::PointF());

        std::unique_ptr<LayerImpl> clip = LayerImpl::Create(layer_tree_impl, 2);
        clip->SetBounds(content_size);
        clip->SetPosition(gfx::PointF());

        std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 3);
        scroll->SetBounds(scroll_content_size);
        scroll->SetScrollClipLayer(clip->id());
        scroll->SetDrawsContent(true);

        std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar = SolidColorScrollbarLayerImpl::Create(layer_tree_impl, 4, VERTICAL, 10, 0,
            false, true);
        scrollbar->SetBounds(scrollbar_size);
        scrollbar->SetPosition(gfx::PointF(345, 0));
        scrollbar->SetScrollLayerId(scroll->id());
        scrollbar->SetDrawsContent(true);
        scrollbar->test_properties()->opacity = 1.f;

        std::unique_ptr<LayerImpl> squash1 = LayerImpl::Create(layer_tree_impl, 5);
        squash1->SetBounds(gfx::Size(140, 300));
        squash1->SetPosition(gfx::PointF(220, 0));
        squash1->SetDrawsContent(true);

        std::unique_ptr<LayerImpl> squash2 = LayerImpl::Create(layer_tree_impl, 6);
        squash2->SetBounds(gfx::Size(140, 300));
        squash2->SetPosition(gfx::PointF(220, 300));
        squash2->SetDrawsContent(true);

        scroll->test_properties()->AddChild(std::move(squash2));
        clip->test_properties()->AddChild(std::move(scroll));
        clip->test_properties()->AddChild(std::move(scrollbar));
        clip->test_properties()->AddChild(std::move(squash1));
        root->test_properties()->AddChild(std::move(clip));

        layer_tree_impl->SetRootLayerForTesting(std::move(root));
        layer_tree_impl->BuildPropertyTreesForTesting();
        layer_tree_impl->DidBecomeActive();

        // The point hits squash1 layer and also scroll layer, because scroll layer is
        // not an ancestor of squash1 layer, we cannot scroll on impl thread.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point(230, 150)).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest,
            status.main_thread_scrolling_reasons);

        // The point hits squash1 layer and also scrollbar layer.
        status = host_impl_->ScrollBegin(BeginState(gfx::Point(350, 150)).get(),
            InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest,
            status.main_thread_scrolling_reasons);

        // The point hits squash2 layer and also scroll layer, because scroll layer is
        // an ancestor of squash2 layer, we should scroll on impl.
        status = host_impl_->ScrollBegin(BeginState(gfx::Point(230, 450)).get(),
            InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
    }

    TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionBasic)
    {
        SetupScrollAndContentsLayers(gfx::Size(200, 200));
        host_impl_->SetViewportSize(gfx::Size(100, 100));

        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
        root->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        DrawFrame();

        // All scroll types inside the non-fast scrollable region should fail.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point(25, 25)).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
            status.main_thread_scrolling_reasons);
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(25, 25),
            InputHandler::WHEEL));

        status = host_impl_->ScrollBegin(BeginState(gfx::Point(25, 25)).get(),
            InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
            status.main_thread_scrolling_reasons);
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
            gfx::Point(25, 25), InputHandler::TOUCHSCREEN));

        // All scroll types outside this region should succeed.
        status = host_impl_->ScrollBegin(BeginState(gfx::Point(75, 75)).get(),
            InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
            gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
            gfx::Point(25, 25), InputHandler::TOUCHSCREEN));
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
            gfx::Point(75, 75), InputHandler::TOUCHSCREEN));

        status = host_impl_->ScrollBegin(BeginState(gfx::Point(75, 75)).get(),
            InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);
        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
            gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
            gfx::Point(75, 75), InputHandler::TOUCHSCREEN));
    }

    TEST_F(LayerTreeHostImplTest, NonFastScrollableRegionWithOffset)
    {
        SetupScrollAndContentsLayers(gfx::Size(200, 200));
        host_impl_->SetViewportSize(gfx::Size(100, 100));

        LayerImpl* root = *host_impl_->active_tree()->begin();
        root->SetNonFastScrollableRegion(gfx::Rect(0, 0, 50, 50));
        root->SetPosition(gfx::PointF(-25.f, 0.f));
        root->SetDrawsContent(true);

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        DrawFrame();

        // This point would fall into the non-fast scrollable region except that we've
        // moved the layer down by 25 pixels.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point(40, 10)).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(40, 10),
            InputHandler::WHEEL));
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 1)).get());
        host_impl_->ScrollEnd(EndState().get());

        // This point is still inside the non-fast region.
        status = host_impl_->ScrollBegin(BeginState(gfx::Point(10, 10)).get(),
            InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNonFastScrollableRegion,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ScrollHandlerNotPresent)
    {
        SetupScrollAndContentsLayers(gfx::Size(200, 200));
        EXPECT_FALSE(host_impl_->active_tree()->have_scroll_event_handlers());
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
        host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
    }

    TEST_F(LayerTreeHostImplTest, ScrollHandlerPresent)
    {
        SetupScrollAndContentsLayers(gfx::Size(200, 200));
        host_impl_->active_tree()->set_have_scroll_event_handlers(true);
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
        host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        EXPECT_TRUE(host_impl_->scroll_affects_scroll_handler());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FALSE(host_impl_->scroll_affects_scroll_handler());
    }

    TEST_F(LayerTreeHostImplTest, ScrollByReturnsCorrectValue)
    {
        SetupScrollAndContentsLayers(gfx::Size(200, 200));
        host_impl_->SetViewportSize(gfx::Size(100, 100));

        DrawFrame();

        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::TOUCHSCREEN);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            status.main_thread_scrolling_reasons);

        // Trying to scroll to the left/top will not succeed.
        EXPECT_FALSE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 0)).get())
                .did_scroll);
        EXPECT_FALSE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, -10)).get())
                .did_scroll);
        EXPECT_FALSE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, -10)).get())
                .did_scroll);

        // Scrolling to the right/bottom will succeed.
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, 0)).get())
                .did_scroll);
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get())
                .did_scroll);
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, 10)).get())
                .did_scroll);

        // Scrolling to left/top will now succeed.
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 0)).get())
                .did_scroll);
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, -10)).get())
                .did_scroll);
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, -10)).get())
                .did_scroll);

        // Scrolling diagonally against an edge will succeed.
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(10, -10)).get())
                .did_scroll);
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 0)).get())
                .did_scroll);
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(-10, 10)).get())
                .did_scroll);

        // Trying to scroll more than the available space will also succeed.
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(5000, 5000)).get())
                .did_scroll);
    }

    TEST_F(LayerTreeHostImplTest, ScrollWithUserUnscrollableLayers)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(200, 200));
        host_impl_->SetViewportSize(gfx::Size(100, 100));

        gfx::Size overflow_size(400, 400);
        ASSERT_EQ(1u, scroll_layer->test_properties()->children.size());
        LayerImpl* overflow = scroll_layer->test_properties()->children[0];
        overflow->SetBounds(overflow_size);
        overflow->SetScrollClipLayer(
            scroll_layer->test_properties()->parent->test_properties()->parent->id());
        overflow->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(overflow->id(),
                gfx::ScrollOffset());
        overflow->SetPosition(gfx::PointF());
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();
        gfx::Point scroll_position(10, 10);

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(scroll_position).get(), InputHandler::WHEEL)
                .thread);
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), overflow->CurrentScrollOffset());

        gfx::Vector2dF scroll_delta(10, 10);
        host_impl_->ScrollBy(UpdateState(scroll_position, scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());

        overflow->set_user_scrollable_horizontal(false);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(scroll_position).get(), InputHandler::WHEEL)
                .thread);
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), overflow->CurrentScrollOffset());

        host_impl_->ScrollBy(UpdateState(scroll_position, scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());

        overflow->set_user_scrollable_vertical(false);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        DrawFrame();

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(scroll_position).get(), InputHandler::WHEEL)
                .thread);
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());

        host_impl_->ScrollBy(UpdateState(scroll_position, scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 10), scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(10, 20), overflow->CurrentScrollOffset());
    }

    TEST_F(LayerTreeHostImplTest, AnimationSchedulingPendingTree)
    {
        EXPECT_FALSE(host_impl_->CommitToActiveTree());

        host_impl_->SetViewportSize(gfx::Size(50, 50));

        host_impl_->CreatePendingTree();
        auto root_owned = LayerImpl::Create(host_impl_->pending_tree(), 1);
        auto* root = root_owned.get();
        host_impl_->pending_tree()->SetRootLayerForTesting(std::move(root_owned));
        root->SetBounds(gfx::Size(50, 50));
        root->test_properties()->force_render_surface = true;

        root->test_properties()->AddChild(
            LayerImpl::Create(host_impl_->pending_tree(), 2));
        LayerImpl* child = root->test_properties()->children[0];
        child->SetBounds(gfx::Size(10, 10));
        child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
        child->SetDrawsContent(true);

        host_impl_->pending_tree()->SetElementIdsForTesting();

        AddAnimatedTransformToElementWithPlayer(child->element_id(), timeline(), 10.0,
            3, 0);
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();

        EXPECT_FALSE(did_request_next_frame_);
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        host_impl_->AnimatePendingTreeAfterCommit();

        // An animation exists on the pending layer. Doing
        // AnimatePendingTreeAfterCommit() requests another frame.
        // In reality, animations without has_set_start_time() == true do not need to
        // be continuously ticked on the pending tree, so it should not request
        // another animation frame here. But we currently do so blindly if any
        // animation exists.
        EXPECT_TRUE(did_request_next_frame_);
        // The pending tree with an animation does not need to draw after animating.
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        did_request_next_frame_ = false;
        did_request_redraw_ = false;
        did_request_commit_ = false;

        host_impl_->ActivateSyncTree();

        // When the animation activates, we should request another animation frame
        // to keep the animation moving.
        EXPECT_TRUE(did_request_next_frame_);
        // On activation we don't need to request a redraw for the animation,
        // activating will draw on its own when it's ready.
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, AnimationSchedulingActiveTree)
    {
        EXPECT_FALSE(host_impl_->CommitToActiveTree());

        host_impl_->SetViewportSize(gfx::Size(50, 50));

        host_impl_->active_tree()->SetRootLayerForTesting(
            LayerImpl::Create(host_impl_->active_tree(), 1));
        LayerImpl* root = *host_impl_->active_tree()->begin();
        root->SetBounds(gfx::Size(50, 50));
        root->test_properties()->force_render_surface = true;

        root->test_properties()->AddChild(
            LayerImpl::Create(host_impl_->active_tree(), 2));
        LayerImpl* child = root->test_properties()->children[0];
        child->SetBounds(gfx::Size(10, 10));
        child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
        child->SetDrawsContent(true);
        host_impl_->active_tree()->SetElementIdsForTesting();

        // Add a translate from 6,7 to 8,9.
        TransformOperations start;
        start.AppendTranslate(6.f, 7.f, 0.f);
        TransformOperations end;
        end.AppendTranslate(8.f, 9.f, 0.f);
        AddAnimatedTransformToElementWithPlayer(child->element_id(), timeline(), 4.0,
            start, end);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        base::TimeTicks now = base::TimeTicks::Now();
        host_impl_->WillBeginImplFrame(
            CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now));

        // TODO(crbug.com/551134): We always request a new frame and a draw for
        // animations that are on the pending tree, but we don't need to do that
        // unless they are waiting for some future time to start.
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
        did_request_next_frame_ = false;
        did_request_redraw_ = false;
        did_request_commit_ = false;

        host_impl_->ActivateAnimations();

        // On activating an animation, we should request another frame so that we'll
        // continue ticking the animation.
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
        did_request_next_frame_ = false;
        did_request_redraw_ = false;
        did_request_commit_ = false;

        // The next frame after activating, we'll tick the animation again.
        host_impl_->Animate();

        // An animation exists on the active layer. Doing Animate() requests another
        // frame after the current one.
        EXPECT_TRUE(did_request_next_frame_);
        // The animation should cause us to draw at the frame's deadline.
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, AnimationSchedulingCommitToActiveTree)
    {
        FakeImplTaskRunnerProvider provider(nullptr);
        CreateHostImplWithTaskRunnerProvider(DefaultSettings(),
            CreateCompositorFrameSink(), &provider);
        EXPECT_TRUE(host_impl_->CommitToActiveTree());

        host_impl_->SetViewportSize(gfx::Size(50, 50));

        auto root_owned = LayerImpl::Create(host_impl_->active_tree(), 1);
        auto* root = root_owned.get();
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_owned));
        root->SetBounds(gfx::Size(50, 50));
        root->SetHasRenderSurface(true);

        auto child_owned = LayerImpl::Create(host_impl_->active_tree(), 2);
        auto* child = child_owned.get();
        root->test_properties()->AddChild(std::move(child_owned));
        child->SetBounds(gfx::Size(10, 10));
        child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
        child->SetDrawsContent(true);

        host_impl_->active_tree()->SetElementIdsForTesting();

        AddAnimatedTransformToElementWithPlayer(child->element_id(), timeline(), 10.0,
            3, 0);

        // Set up the property trees so that UpdateDrawProperties will work in
        // CommitComplete below.
        LayerImplList list;
        LayerTreeHostCommon::CalcDrawPropsImplInputsForTesting inputs(
            root, gfx::Size(50, 50), &list);
        LayerTreeHostCommon::CalculateDrawPropertiesForTesting(&inputs);

        EXPECT_FALSE(did_request_next_frame_);
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        host_impl_->CommitComplete();

        // Animations on the active tree should be started and ticked, and a new frame
        // should be requested to continue ticking them.
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        // Delete the LayerTreeHostImpl before the TaskRunnerProvider goes away.
        host_impl_->ReleaseCompositorFrameSink();
        host_impl_ = nullptr;
    }

    TEST_F(LayerTreeHostImplTest, AnimationSchedulingOnLayerDestruction)
    {
        host_impl_->SetViewportSize(gfx::Size(50, 50));

        host_impl_->active_tree()->SetRootLayerForTesting(
            LayerImpl::Create(host_impl_->active_tree(), 1));
        LayerImpl* root = *host_impl_->active_tree()->begin();
        root->SetBounds(gfx::Size(50, 50));

        root->test_properties()->AddChild(
            LayerImpl::Create(host_impl_->active_tree(), 2));
        LayerImpl* child = root->test_properties()->children[0];
        child->SetBounds(gfx::Size(10, 10));
        child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
        child->SetDrawsContent(true);

        host_impl_->active_tree()->SetElementIdsForTesting();

        // Add a translate animation.
        TransformOperations start;
        start.AppendTranslate(6.f, 7.f, 0.f);
        TransformOperations end;
        end.AppendTranslate(8.f, 9.f, 0.f);
        AddAnimatedTransformToElementWithPlayer(child->element_id(), timeline(), 4.0,
            start, end);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        base::TimeTicks now = base::TimeTicks::Now();
        host_impl_->WillBeginImplFrame(
            CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now));
        EXPECT_TRUE(did_request_next_frame_);
        did_request_next_frame_ = false;

        host_impl_->ActivateAnimations();
        // On activating an animation, we should request another frame so that we'll
        // continue ticking the animation.
        EXPECT_TRUE(did_request_next_frame_);
        did_request_next_frame_ = false;

        // The next frame after activating, we'll tick the animation again.
        host_impl_->Animate();
        // An animation exists on the active layer. Doing Animate() requests another
        // frame after the current one.
        EXPECT_TRUE(did_request_next_frame_);
        did_request_next_frame_ = false;

        // Destroy layer, unregister animation target (element).
        child->test_properties()->parent = nullptr;
        root->test_properties()->RemoveChild(child);
        child = nullptr;

        // Doing Animate() doesn't request another frame after the current one.
        host_impl_->Animate();
        EXPECT_FALSE(did_request_next_frame_);

        host_impl_->Animate();
        EXPECT_FALSE(did_request_next_frame_);
    }

    class MissingTilesLayer : public LayerImpl {
    public:
        MissingTilesLayer(LayerTreeImpl* layer_tree_impl, int id)
            : LayerImpl(layer_tree_impl, id)
            , has_missing_tiles_(true)
        {
        }

        void set_has_missing_tiles(bool has_missing_tiles)
        {
            has_missing_tiles_ = has_missing_tiles;
        }

        void AppendQuads(RenderPass* render_pass,
            AppendQuadsData* append_quads_data) override
        {
            append_quads_data->num_missing_tiles += has_missing_tiles_;
        }

    private:
        bool has_missing_tiles_;
    };

    TEST_F(LayerTreeHostImplTest, AnimationMarksLayerNotReady)
    {
        host_impl_->SetViewportSize(gfx::Size(50, 50));

        host_impl_->active_tree()->SetRootLayerForTesting(
            LayerImpl::Create(host_impl_->active_tree(), 1));
        LayerImpl* root = *host_impl_->active_tree()->begin();
        root->SetBounds(gfx::Size(50, 50));
        root->SetHasRenderSurface(true);

        root->test_properties()->AddChild(std::unique_ptr<MissingTilesLayer>(
            new MissingTilesLayer(host_impl_->active_tree(), 2)));
        MissingTilesLayer* child = static_cast<MissingTilesLayer*>(root->test_properties()->children[0]);
        child->SetBounds(gfx::Size(10, 10));
        child->draw_properties().visible_layer_rect = gfx::Rect(10, 10);
        child->SetDrawsContent(true);

        host_impl_->active_tree()->SetElementIdsForTesting();

        EXPECT_TRUE(child->was_ever_ready_since_last_transform_animation());

        // Add a translate from 6,7 to 8,9.
        TransformOperations start;
        start.AppendTranslate(6.f, 7.f, 0.f);
        TransformOperations end;
        end.AppendTranslate(8.f, 9.f, 0.f);
        int animation_id = AddAnimatedTransformToElementWithPlayer(
            child->element_id(), timeline(), 4.0, start, end);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        base::TimeTicks now = base::TimeTicks::Now();
        host_impl_->WillBeginImplFrame(
            CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, now));

        host_impl_->ActivateAnimations();
        host_impl_->Animate();

        EXPECT_FALSE(child->was_ever_ready_since_last_transform_animation());

        host_impl_->ResetRequiresHighResToDraw();

        // Child layer has an animating transform but missing tiles.
        FakeLayerTreeHostImpl::FrameData frame;
        DrawResult result = host_impl_->PrepareToDraw(&frame);
        EXPECT_EQ(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS, result);
        host_impl_->DidDrawAllLayers(frame);

        child->set_has_missing_tiles(false);

        // Child layer has an animating and no missing tiles.
        result = host_impl_->PrepareToDraw(&frame);
        EXPECT_EQ(DRAW_SUCCESS, result);
        EXPECT_TRUE(child->was_ever_ready_since_last_transform_animation());
        host_impl_->DidDrawAllLayers(frame);

        // Remove the animation.
        child->set_has_missing_tiles(true);
        RemoveAnimationFromElementWithExistingPlayer(child->element_id(), timeline(),
            animation_id);
        child->draw_properties().screen_space_transform_is_animating = false;

        // Child layer doesn't have an animation, but was never ready since the last
        // time it animated (and has missing tiles).
        result = host_impl_->PrepareToDraw(&frame);
        EXPECT_EQ(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS, result);
        EXPECT_FALSE(child->was_ever_ready_since_last_transform_animation());
        host_impl_->DidDrawAllLayers(frame);

        child->set_has_missing_tiles(false);

        // Child layer doesn't have an animation and all tiles are ready.
        result = host_impl_->PrepareToDraw(&frame);
        EXPECT_EQ(DRAW_SUCCESS, result);
        EXPECT_TRUE(child->was_ever_ready_since_last_transform_animation());
        host_impl_->DidDrawAllLayers(frame);

        child->set_has_missing_tiles(true);

        // Child layer doesn't have an animation, and was ready at least once since
        // the last time it animated.
        result = host_impl_->PrepareToDraw(&frame);
        EXPECT_EQ(DRAW_SUCCESS, result);
        EXPECT_TRUE(child->was_ever_ready_since_last_transform_animation());
        host_impl_->DidDrawAllLayers(frame);
    }

    TEST_F(LayerTreeHostImplTest, ImplPinchZoom)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        EXPECT_EQ(scroll_layer, host_impl_->InnerViewportScrollLayer());
        LayerImpl* container_layer = scroll_layer->scroll_clip_layer();
        EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());

        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 1.f;

        // The impl-based pinch zoom should adjust the max scroll position.
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(
                page_scale_factor, min_page_scale, max_page_scale);
            host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());

            float page_scale_delta = 2.f;

            host_impl_->ScrollBegin(BeginState(gfx::Point(50, 50)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_TRUE(did_request_redraw_);
            EXPECT_TRUE(did_request_commit_);
            EXPECT_EQ(gfx::Size(50, 50), container_layer->bounds());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);

            EXPECT_EQ(gfx::ScrollOffset(75.0, 75.0).ToString(),
                scroll_layer->MaxScrollOffset().ToString());
        }

        // Scrolling after a pinch gesture should always be in local space.  The
        // scroll deltas have the page scale factor applied.
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(
                page_scale_factor, min_page_scale, max_page_scale);
            host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());

            float page_scale_delta = 2.f;
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            gfx::Vector2d scroll_delta(0, 10);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::WHEEL)
                    .thread);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_TRUE(ScrollInfoContains(
                *scroll_info.get(), scroll_layer->id(),
                gfx::Vector2d(0, scroll_delta.y() / page_scale_delta)));
        }
    }

    TEST_F(LayerTreeHostImplTest, ViewportScrollOrder)
    {
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.25f, 4.f);

        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        outer_scroll_layer->SetDrawsContent(true);
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
        inner_scroll_layer->SetDrawsContent(true);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        EXPECT_VECTOR_EQ(gfx::Vector2dF(500, 500),
            outer_scroll_layer->MaxScrollOffset());

        host_impl_->ScrollBegin(BeginState(gfx::Point(250, 250)).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2.f, gfx::Point(0, 0));
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        // Sanity check - we're zoomed in, starting from the origin.
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            inner_scroll_layer->CurrentScrollOffset());

        // Scroll down - only the inner viewport should scroll.
        host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100.f, 100.f)).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());

        // Scroll down - outer viewport should start scrolling after the inner is at
        // its maximum.
        host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(0, 0), gfx::Vector2dF(1000.f, 1000.f)).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(gfx::Vector2dF(250, 250),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(300, 300),
            outer_scroll_layer->CurrentScrollOffset());
    }

    // Make sure scrolls smaller than a unit applied to the viewport don't get
    // dropped. crbug.com/539334.
    TEST_F(LayerTreeHostImplTest, ScrollViewportWithFractionalAmounts)
    {
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);

        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        outer_scroll_layer->SetDrawsContent(true);
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
        inner_scroll_layer->SetDrawsContent(true);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        // Sanity checks.
        EXPECT_VECTOR_EQ(gfx::Vector2dF(500, 500),
            outer_scroll_layer->MaxScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), inner_scroll_layer->CurrentScrollOffset());

        // Scroll only the layout viewport.
        host_impl_->ScrollBegin(BeginState(gfx::Point(250, 250)).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(250, 250), gfx::Vector2dF(0.125f, 0.125f)).get());
        EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0.125f, 0.125f),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0, 0),
            inner_scroll_layer->CurrentScrollOffset());
        host_impl_->ScrollEnd(EndState().get());

        host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 1.f, 2.f);

        // Now that we zoomed in, the scroll should be applied to the inner viewport.
        host_impl_->ScrollBegin(BeginState(gfx::Point(250, 250)).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(250, 250), gfx::Vector2dF(0.5f, 0.5f)).get());
        EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0.125f, 0.125f),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0.25f, 0.25f),
            inner_scroll_layer->CurrentScrollOffset());
        host_impl_->ScrollEnd(EndState().get());
    }

    // Tests that scrolls during a pinch gesture (i.e. "two-finger" scrolls) work
    // as expected. That is, scrolling during a pinch should bubble from the inner
    // to the outer viewport.
    TEST_F(LayerTreeHostImplTest, ScrollDuringPinchGesture)
    {
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);

        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        outer_scroll_layer->SetDrawsContent(true);
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();
        inner_scroll_layer->SetDrawsContent(true);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        EXPECT_VECTOR_EQ(gfx::Vector2dF(500, 500),
            outer_scroll_layer->MaxScrollOffset());

        host_impl_->ScrollBegin(BeginState(gfx::Point(250, 250)).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();

        host_impl_->PinchGestureUpdate(2, gfx::Point(250, 250));
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(125, 125),
            inner_scroll_layer->CurrentScrollOffset());

        // Needed so that the pinch is accounted for in draw properties.
        DrawFrame();

        host_impl_->ScrollBy(
            UpdateState(gfx::Point(250, 250), gfx::Vector2dF(10.f, 10.f)).get());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(130, 130),
            inner_scroll_layer->CurrentScrollOffset());

        DrawFrame();

        host_impl_->ScrollBy(
            UpdateState(gfx::Point(250, 250), gfx::Vector2dF(400.f, 400.f)).get());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(80, 80),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(250, 250),
            inner_scroll_layer->CurrentScrollOffset());

        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());
    }

    // Tests the "snapping" of pinch-zoom gestures to the screen edge. That is, when
    // a pinch zoom is anchored within a certain margin of the screen edge, we
    // should assume the user means to scroll into the edge of the screen.
    TEST_F(LayerTreeHostImplTest, PinchZoomSnapsToScreenEdge)
    {
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);

        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        int offsetFromEdge = Viewport::kPinchZoomSnapMarginDips - 5;
        gfx::Point anchor(viewport_size.width() - offsetFromEdge,
            viewport_size.height() - offsetFromEdge);

        // Pinch in within the margins. The scroll should stay exactly locked to the
        // bottom and right.
        host_impl_->ScrollBegin(BeginState(anchor).get(), InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2, anchor);
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(
            gfx::Vector2dF(250, 250),
            host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());

        // Reset.
        host_impl_->active_tree()->SetPageScaleOnActiveTree(1.f);
        SetScrollOffsetDelta(host_impl_->InnerViewportScrollLayer(), gfx::Vector2d());
        SetScrollOffsetDelta(host_impl_->OuterViewportScrollLayer(), gfx::Vector2d());

        // Pinch in within the margins. The scroll should stay exactly locked to the
        // top and left.
        anchor = gfx::Point(offsetFromEdge, offsetFromEdge);
        host_impl_->ScrollBegin(BeginState(anchor).get(), InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2, anchor);
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(
            gfx::Vector2dF(0, 0),
            host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());

        // Reset.
        host_impl_->active_tree()->SetPageScaleOnActiveTree(1.f);
        SetScrollOffsetDelta(host_impl_->InnerViewportScrollLayer(), gfx::Vector2d());
        SetScrollOffsetDelta(host_impl_->OuterViewportScrollLayer(), gfx::Vector2d());

        // Pinch in just outside the margin. There should be no snapping.
        offsetFromEdge = Viewport::kPinchZoomSnapMarginDips;
        anchor = gfx::Point(offsetFromEdge, offsetFromEdge);
        host_impl_->ScrollBegin(BeginState(anchor).get(), InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2, anchor);
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(
            gfx::Vector2dF(50, 50),
            host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());

        // Reset.
        host_impl_->active_tree()->SetPageScaleOnActiveTree(1.f);
        SetScrollOffsetDelta(host_impl_->InnerViewportScrollLayer(), gfx::Vector2d());
        SetScrollOffsetDelta(host_impl_->OuterViewportScrollLayer(), gfx::Vector2d());

        // Pinch in just outside the margin. There should be no snapping.
        offsetFromEdge = Viewport::kPinchZoomSnapMarginDips;
        anchor = gfx::Point(viewport_size.width() - offsetFromEdge,
            viewport_size.height() - offsetFromEdge);
        host_impl_->ScrollBegin(BeginState(anchor).get(), InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2, anchor);
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(
            gfx::Vector2dF(200, 200),
            host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
    }

    TEST_F(LayerTreeHostImplTest, ImplPinchZoomWheelBubbleBetweenViewports)
    {
        const gfx::Size content_size(200, 200);
        const gfx::Size viewport_size(100, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();

        // Zoom into the page by a 2X factor
        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 2.f;
        host_impl_->active_tree()->PushPageScaleFromMainThread(
            page_scale_factor, min_page_scale, max_page_scale);
        host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);

        // Scroll by a small amount, there should be no bubbling to the outer
        // viewport.
        host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
            InputHandler::WHEEL);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(0, 0), gfx::Vector2dF(10.f, 20.f)).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 10),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), outer_scroll_layer->CurrentScrollOffset());

        // Scroll by the inner viewport's max scroll extent, the remainder
        // should bubble up to the outer viewport.
        host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
            InputHandler::WHEEL);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100.f, 100.f)).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 10),
            outer_scroll_layer->CurrentScrollOffset());

        // Scroll by the outer viewport's max scroll extent, it should all go to the
        // outer viewport.
        host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
            InputHandler::WHEEL);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(0, 0), gfx::Vector2dF(190.f, 180.f)).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_VECTOR_EQ(gfx::Vector2dF(100, 100),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50),
            inner_scroll_layer->CurrentScrollOffset());
    }

    TEST_F(LayerTreeHostImplTest, ScrollWithSwapPromises)
    {
        ui::LatencyInfo latency_info;
        latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0,
            1234);
        std::unique_ptr<SwapPromise> swap_promise(
            new LatencyInfoSwapPromise(latency_info));

        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        host_impl_->QueueSwapPromiseForMainThreadScrollUpdate(
            std::move(swap_promise));
        host_impl_->ScrollEnd(EndState().get());

        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_EQ(1u, scroll_info->swap_promises.size());
        EXPECT_EQ(latency_info.trace_id(), scroll_info->swap_promises[0]->TraceId());
    }

    // Test that scrolls targeting a layer with a non-null scroll_parent() don't
    // bubble up.
    TEST_F(LayerTreeHostImplTest, ScrollDoesntBubble)
    {
        LayerImpl* viewport_scroll = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));

        // Set up two scrolling children of the root, one of which is a scroll parent
        // to the other. Scrolls shouldn't bubbling from the child.
        LayerImpl* parent;
        LayerImpl* child;
        LayerImpl* child_clip;

        std::unique_ptr<LayerImpl> scroll_parent_clip = LayerImpl::Create(host_impl_->active_tree(), 6);
        std::unique_ptr<LayerImpl> scroll_parent = CreateScrollableLayer(7, gfx::Size(10, 10), scroll_parent_clip.get());
        parent = scroll_parent.get();
        scroll_parent_clip->test_properties()->AddChild(std::move(scroll_parent));

        viewport_scroll->test_properties()->AddChild(std::move(scroll_parent_clip));

        std::unique_ptr<LayerImpl> scroll_child_clip = LayerImpl::Create(host_impl_->active_tree(), 8);
        std::unique_ptr<LayerImpl> scroll_child = CreateScrollableLayer(9, gfx::Size(10, 10), scroll_child_clip.get());
        child = scroll_child.get();
        scroll_child->SetPosition(gfx::PointF(20.f, 20.f));
        scroll_child_clip->test_properties()->AddChild(std::move(scroll_child));

        child_clip = scroll_child_clip.get();
        viewport_scroll->test_properties()->AddChild(std::move(scroll_child_clip));

        child_clip->test_properties()->scroll_parent = parent;
        std::unique_ptr<std::set<LayerImpl*>> scroll_children(
            new std::set<LayerImpl*>);
        scroll_children->insert(child_clip);
        parent->test_properties()->scroll_children.reset(scroll_children.release());
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(21, 21)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(21, 21), gfx::Vector2d(5, 5)).get());
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(21, 21), gfx::Vector2d(100, 100)).get());
            host_impl_->ScrollEnd(EndState().get());

            // The child should be fully scrolled by the first ScrollBy.
            EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 5), child->CurrentScrollOffset());

            // The scroll_parent shouldn't receive the second ScrollBy.
            EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0), parent->CurrentScrollOffset());

            // The viewport shouldn't have been scrolled at all.
            EXPECT_VECTOR_EQ(
                gfx::Vector2dF(0, 0),
                host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(
                gfx::Vector2dF(0, 0),
                host_impl_->OuterViewportScrollLayer()->CurrentScrollOffset());
        }

        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(21, 21)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(21, 21), gfx::Vector2d(3, 4)).get());
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(21, 21), gfx::Vector2d(2, 1)).get());
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(21, 21), gfx::Vector2d(2, 1)).get());
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(21, 21), gfx::Vector2d(2, 1)).get());
            host_impl_->ScrollEnd(EndState().get());

            // The ScrollBy's should scroll the parent to its extent.
            EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 5), parent->CurrentScrollOffset());

            // The viewport shouldn't receive any scroll delta.
            EXPECT_VECTOR_EQ(
                gfx::Vector2dF(0, 0),
                host_impl_->InnerViewportScrollLayer()->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(
                gfx::Vector2dF(0, 0),
                host_impl_->OuterViewportScrollLayer()->CurrentScrollOffset());
        }
    }

    TEST_F(LayerTreeHostImplTest, PinchGesture)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
        DCHECK(scroll_layer);

        float min_page_scale = 1.f;
        float max_page_scale = 4.f;

        // Basic pinch zoom in gesture
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());

            float page_scale_delta = 2.f;
            host_impl_->ScrollBegin(BeginState(gfx::Point(50, 50)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_TRUE(did_request_redraw_);
            EXPECT_TRUE(did_request_commit_);

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
        }

        // Zoom-in clamping
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
            float page_scale_delta = 10.f;

            host_impl_->ScrollBegin(BeginState(gfx::Point(50, 50)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(50, 50));
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, max_page_scale);
        }

        // Zoom-out clamping
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.CollectScrollDeltasForTesting();
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    scroll_layer->id(), gfx::ScrollOffset(50, 50));

            float page_scale_delta = 0.1f;
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, min_page_scale);

            EXPECT_TRUE(scroll_info->scrolls.empty());
        }

        // Two-finger panning should not happen based on pinch events only
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.CollectScrollDeltasForTesting();
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    scroll_layer->id(), gfx::ScrollOffset(20, 20));

            float page_scale_delta = 1.f;
            host_impl_->ScrollBegin(BeginState(gfx::Point(10, 10)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(10, 10));
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(20, 20));
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
            EXPECT_TRUE(scroll_info->scrolls.empty());
        }

        // Two-finger panning should work with interleaved scroll events
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.CollectScrollDeltasForTesting();
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    scroll_layer->id(), gfx::ScrollOffset(20, 20));

            float page_scale_delta = 1.f;
            host_impl_->ScrollBegin(BeginState(gfx::Point(10, 10)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(10, 10));
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(10, 10), gfx::Vector2d(-10, -10)).get());
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point(20, 20));
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, page_scale_delta);
            EXPECT_TRUE(ScrollInfoContains(*scroll_info, scroll_layer->id(),
                gfx::Vector2d(-10, -10)));
        }

        // Two-finger panning should work when starting fully zoomed out.
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(0.5f, 0.5f, 4.f);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.CollectScrollDeltasForTesting();
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->id(),
                    gfx::ScrollOffset(0, 0));

            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(2.f, gfx::Point(0, 0));
            host_impl_->PinchGestureUpdate(1.f, gfx::Point(0, 0));

            // Needed so layer transform includes page scale.
            DrawFrame();

            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2d(10, 10)).get());
            host_impl_->PinchGestureUpdate(1.f, gfx::Point(10, 10));
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, 2.f);
            EXPECT_TRUE(ScrollInfoContains(*scroll_info, scroll_layer->id(),
                gfx::Vector2d(10, 10)));
        }
    }

    TEST_F(LayerTreeHostImplTest, PageScaleAnimation)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
        DCHECK(scroll_layer);

        float min_page_scale = 0.5f;
        float max_page_scale = 4.f;
        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromSeconds(1);
        base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
        base::TimeTicks halfway_through_animation = start_time + duration / 2;
        base::TimeTicks end_time = start_time + duration;

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        // Non-anchor zoom-in
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    scroll_layer->id(), gfx::ScrollOffset(50, 50));

            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            host_impl_->active_tree()->SetPendingPageScaleAnimation(
                std::unique_ptr<PendingPageScaleAnimation>(
                    new PendingPageScaleAnimation(gfx::Vector2d(), false, 2.f,
                        duration)));
            host_impl_->ActivateSyncTree();
            EXPECT_FALSE(did_request_redraw_);
            EXPECT_TRUE(did_request_next_frame_);

            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            begin_frame_args.frame_time = start_time;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_redraw_);
            EXPECT_TRUE(did_request_next_frame_);
            host_impl_->DidFinishImplFrame();

            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            begin_frame_args.frame_time = halfway_through_animation;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_redraw_);
            EXPECT_TRUE(did_request_next_frame_);
            host_impl_->DidFinishImplFrame();

            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            did_request_commit_ = false;
            begin_frame_args.frame_time = end_time;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_commit_);
            EXPECT_FALSE(did_request_next_frame_);
            host_impl_->DidFinishImplFrame();

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, 2);
            EXPECT_TRUE(ScrollInfoContains(*scroll_info, scroll_layer->id(),
                gfx::Vector2d(-50, -50)));
        }

        start_time += base::TimeDelta::FromSeconds(10);
        halfway_through_animation += base::TimeDelta::FromSeconds(10);
        end_time += base::TimeDelta::FromSeconds(10);

        // Anchor zoom-out
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    scroll_layer->id(), gfx::ScrollOffset(50, 50));

            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            host_impl_->active_tree()->SetPendingPageScaleAnimation(
                std::unique_ptr<PendingPageScaleAnimation>(
                    new PendingPageScaleAnimation(gfx::Vector2d(25, 25), true,
                        min_page_scale, duration)));
            host_impl_->ActivateSyncTree();
            EXPECT_FALSE(did_request_redraw_);
            EXPECT_TRUE(did_request_next_frame_);

            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            begin_frame_args.frame_time = start_time;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_redraw_);
            EXPECT_TRUE(did_request_next_frame_);
            host_impl_->DidFinishImplFrame();

            did_request_redraw_ = false;
            did_request_commit_ = false;
            did_request_next_frame_ = false;
            begin_frame_args.frame_time = end_time;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_redraw_);
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_TRUE(did_request_commit_);
            host_impl_->DidFinishImplFrame();

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, min_page_scale);
            // Pushed to (0,0) via clamping against contents layer size.
            EXPECT_TRUE(ScrollInfoContains(*scroll_info, scroll_layer->id(),
                gfx::Vector2d(-50, -50)));
        }
    }

    TEST_F(LayerTreeHostImplTest, PageScaleAnimationNoOp)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
        DCHECK(scroll_layer);

        float min_page_scale = 0.5f;
        float max_page_scale = 4.f;
        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromSeconds(1);
        base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
        base::TimeTicks halfway_through_animation = start_time + duration / 2;
        base::TimeTicks end_time = start_time + duration;

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        // Anchor zoom with unchanged page scale should not change scroll or scale.
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
                max_page_scale);
            scroll_layer->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    scroll_layer->id(), gfx::ScrollOffset(50, 50));

            host_impl_->active_tree()->SetPendingPageScaleAnimation(
                std::unique_ptr<PendingPageScaleAnimation>(
                    new PendingPageScaleAnimation(gfx::Vector2d(), true, 1.f,
                        duration)));
            host_impl_->ActivateSyncTree();
            begin_frame_args.frame_time = start_time;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            host_impl_->DidFinishImplFrame();

            begin_frame_args.frame_time = halfway_through_animation;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_redraw_);
            host_impl_->DidFinishImplFrame();

            begin_frame_args.frame_time = end_time;
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_TRUE(did_request_commit_);
            host_impl_->DidFinishImplFrame();

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_EQ(scroll_info->page_scale_delta, 1);
            ExpectNone(*scroll_info, scroll_layer->id());
        }
    }

    TEST_F(LayerTreeHostImplTest, PageScaleAnimationTransferedOnSyncTreeActivate)
    {
        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
        CreateScrollAndContentsLayers(host_impl_->pending_tree(),
            gfx::Size(100, 100));
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();
        host_impl_->ActivateSyncTree();
        DrawFrame();

        LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
        DCHECK(scroll_layer);

        float min_page_scale = 0.5f;
        float max_page_scale = 4.f;
        host_impl_->sync_tree()->PushPageScaleFromMainThread(1.f, min_page_scale,
            max_page_scale);
        host_impl_->ActivateSyncTree();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromSeconds(1);
        base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
        base::TimeTicks third_through_animation = start_time + duration / 3;
        base::TimeTicks halfway_through_animation = start_time + duration / 2;
        base::TimeTicks end_time = start_time + duration;
        float target_scale = 2.f;

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->id(),
                gfx::ScrollOffset(50, 50));

        // Make sure TakePageScaleAnimation works properly.

        host_impl_->sync_tree()->SetPendingPageScaleAnimation(
            std::unique_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
                gfx::Vector2d(), false, target_scale, duration)));
        std::unique_ptr<PendingPageScaleAnimation> psa = host_impl_->sync_tree()->TakePendingPageScaleAnimation();
        EXPECT_EQ(target_scale, psa->scale);
        EXPECT_EQ(duration, psa->duration);
        EXPECT_EQ(nullptr, host_impl_->sync_tree()->TakePendingPageScaleAnimation());

        // Recreate the PSA. Nothing should happen here since the tree containing the
        // PSA hasn't been activated yet.
        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        host_impl_->sync_tree()->SetPendingPageScaleAnimation(
            std::unique_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
                gfx::Vector2d(), false, target_scale, duration)));
        begin_frame_args.frame_time = halfway_through_animation;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_FALSE(did_request_next_frame_);
        EXPECT_FALSE(did_request_redraw_);
        host_impl_->DidFinishImplFrame();

        // Activate the sync tree. This should cause the animation to become enabled.
        // It should also clear the pointer on the sync tree.
        host_impl_->ActivateSyncTree();
        EXPECT_EQ(nullptr,
            host_impl_->sync_tree()->TakePendingPageScaleAnimation().get());
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_TRUE(did_request_next_frame_);

        start_time += base::TimeDelta::FromSeconds(10);
        third_through_animation += base::TimeDelta::FromSeconds(10);
        halfway_through_animation += base::TimeDelta::FromSeconds(10);
        end_time += base::TimeDelta::FromSeconds(10);

        // From here on, make sure the animation runs as normal.
        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_TRUE(did_request_next_frame_);
        host_impl_->DidFinishImplFrame();

        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        begin_frame_args.frame_time = third_through_animation;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_TRUE(did_request_next_frame_);
        host_impl_->DidFinishImplFrame();

        // Another activation shouldn't have any effect on the animation.
        host_impl_->ActivateSyncTree();

        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        begin_frame_args.frame_time = halfway_through_animation;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_TRUE(did_request_next_frame_);
        host_impl_->DidFinishImplFrame();

        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        did_request_commit_ = false;
        begin_frame_args.frame_time = end_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_TRUE(did_request_commit_);
        EXPECT_FALSE(did_request_next_frame_);
        host_impl_->DidFinishImplFrame();

        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_EQ(scroll_info->page_scale_delta, target_scale);
        EXPECT_TRUE(ScrollInfoContains(*scroll_info, scroll_layer->id(),
            gfx::Vector2d(-50, -50)));
    }

    TEST_F(LayerTreeHostImplTest, PageScaleAnimationCompletedNotification)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        LayerImpl* scroll_layer = host_impl_->InnerViewportScrollLayer();
        DCHECK(scroll_layer);

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromSeconds(1);
        base::TimeDelta duration = base::TimeDelta::FromMilliseconds(100);
        base::TimeTicks halfway_through_animation = start_time + duration / 2;
        base::TimeTicks end_time = start_time + duration;

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->id(),
                gfx::ScrollOffset(50, 50));

        did_complete_page_scale_animation_ = false;
        host_impl_->active_tree()->SetPendingPageScaleAnimation(
            std::unique_ptr<PendingPageScaleAnimation>(new PendingPageScaleAnimation(
                gfx::Vector2d(), false, 2.f, duration)));
        host_impl_->ActivateSyncTree();
        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_FALSE(did_complete_page_scale_animation_);
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = halfway_through_animation;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_FALSE(did_complete_page_scale_animation_);
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = end_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_TRUE(did_complete_page_scale_animation_);
        host_impl_->DidFinishImplFrame();
    }

    TEST_F(LayerTreeHostImplTest, MaxScrollOffsetAffectedByBoundsDelta)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        DrawFrame();

        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
        LayerImpl* inner_container = inner_scroll->scroll_clip_layer();
        DCHECK(inner_scroll);
        DCHECK(inner_container);
        EXPECT_EQ(gfx::ScrollOffset(50, 50), inner_scroll->MaxScrollOffset());

        inner_container->SetBoundsDelta(gfx::Vector2dF(15.f, 15.f));
        inner_scroll->SetBoundsDelta(gfx::Vector2dF(7.f, 7.f));
        EXPECT_EQ(gfx::ScrollOffset(42, 42), inner_scroll->MaxScrollOffset());

        inner_container->SetBoundsDelta(gfx::Vector2dF());
        inner_scroll->SetBoundsDelta(gfx::Vector2dF());
        inner_scroll->SetBounds(gfx::Size());
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        DrawFrame();

        inner_scroll->SetBoundsDelta(gfx::Vector2dF(60.f, 60.f));
        EXPECT_EQ(gfx::ScrollOffset(10, 10), inner_scroll->MaxScrollOffset());
    }

    class LayerTreeHostImplOverridePhysicalTime : public LayerTreeHostImpl {
    public:
        LayerTreeHostImplOverridePhysicalTime(
            const LayerTreeSettings& settings,
            LayerTreeHostImplClient* client,
            TaskRunnerProvider* task_runner_provider,
            TaskGraphRunner* task_graph_runner,
            RenderingStatsInstrumentation* rendering_stats_instrumentation)
            : LayerTreeHostImpl(settings,
                client,
                task_runner_provider,
                rendering_stats_instrumentation,
                task_graph_runner,
                AnimationHost::CreateForTesting(ThreadInstance::IMPL),
                0,
                nullptr)
        {
        }

        BeginFrameArgs CurrentBeginFrameArgs() const override
        {
            return CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1,
                fake_current_physical_time_);
        }

        void SetCurrentPhysicalTimeTicksForTest(base::TimeTicks fake_now)
        {
            fake_current_physical_time_ = fake_now;
        }

    private:
        base::TimeTicks fake_current_physical_time_;
    };

    class LayerTreeHostImplTestScrollbarAnimation : public LayerTreeHostImplTest {
    protected:
        void SetupLayers(LayerTreeSettings settings)
        {
            host_impl_->ReleaseCompositorFrameSink();
            host_impl_ = nullptr;

            gfx::Size content_size(100, 100);

            LayerTreeHostImplOverridePhysicalTime* host_impl_override_time = new LayerTreeHostImplOverridePhysicalTime(
                settings, this, &task_runner_provider_, &task_graph_runner_,
                &stats_instrumentation_);
            host_impl_ = base::WrapUnique(host_impl_override_time);
            compositor_frame_sink_ = CreateCompositorFrameSink();
            host_impl_->SetVisible(true);
            host_impl_->InitializeRenderer(compositor_frame_sink_.get());

            SetupScrollAndContentsLayers(content_size);
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 4.f);
            host_impl_->SetViewportSize(
                gfx::Size(content_size.width() / 2, content_size.height() / 2));

            std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 400,
                VERTICAL, 10, 0, false, true);
            scrollbar->test_properties()->opacity = 0.f;
            EXPECT_FLOAT_EQ(0.f, scrollbar->test_properties()->opacity);

            LayerImpl* scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
            LayerImpl* root = host_impl_->active_tree()->InnerViewportContainerLayer();
            scrollbar->SetScrollLayerId(scroll->id());
            root->test_properties()->AddChild(std::move(scrollbar));
            host_impl_->active_tree()->BuildPropertyTreesForTesting();
            host_impl_->active_tree()->DidBecomeActive();
            DrawFrame();
        }

        void RunTest(LayerTreeSettings::ScrollbarAnimator animator)
        {
            LayerTreeSettings settings = DefaultSettings();
            settings.scrollbar_animator = animator;
            settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20);
            settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(20);

            // If no animator is set, scrollbar won't show and no animation is expected.
            bool expecting_animations = animator != LayerTreeSettings::NO_ANIMATOR;

            SetupLayers(settings);

            base::TimeTicks fake_now = base::TimeTicks::Now();

            if (expecting_animations) {
                // A task will be posted to fade the initial scrollbar.
                EXPECT_FALSE(did_request_next_frame_);
                EXPECT_FALSE(did_request_redraw_);
                EXPECT_FALSE(animation_task_.Equals(base::Closure()));
                requested_animation_delay_ = base::TimeDelta();
                animation_task_ = base::Closure();
            } else {
                EXPECT_FALSE(did_request_next_frame_);
                EXPECT_FALSE(did_request_redraw_);
                EXPECT_TRUE(animation_task_.Equals(base::Closure()));
                EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
            }

            // If no scroll happened during a scroll gesture, it should have no effect.
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::WHEEL);
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_FALSE(did_request_redraw_);
            EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
            EXPECT_TRUE(animation_task_.Equals(base::Closure()));

            // Before the scrollbar animation exists, we should not get redraws.
            BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2, fake_now);
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_FALSE(did_request_next_frame_);
            did_request_next_frame_ = false;
            EXPECT_FALSE(did_request_redraw_);
            did_request_redraw_ = false;
            EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
            EXPECT_TRUE(animation_task_.Equals(base::Closure()));
            host_impl_->DidFinishImplFrame();

            // After a scroll, a scrollbar animation should be scheduled about 20ms from
            // now.
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::WHEEL);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2dF(0, 5)).get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_TRUE(did_request_redraw_);
            did_request_redraw_ = false;
            EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
            EXPECT_TRUE(animation_task_.Equals(base::Closure()));

            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_FALSE(did_request_redraw_);
            if (expecting_animations) {
                EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
                    requested_animation_delay_);
                EXPECT_FALSE(animation_task_.Equals(base::Closure()));
            } else {
                EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
                EXPECT_TRUE(animation_task_.Equals(base::Closure()));
            }

            if (expecting_animations) {
                // Before the scrollbar animation begins, we should not get redraws.
                begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 3, fake_now);
                host_impl_->WillBeginImplFrame(begin_frame_args);
                host_impl_->Animate();
                EXPECT_FALSE(did_request_next_frame_);
                did_request_next_frame_ = false;
                EXPECT_FALSE(did_request_redraw_);
                did_request_redraw_ = false;
                host_impl_->DidFinishImplFrame();

                // Start the scrollbar animation.
                fake_now += requested_animation_delay_;
                requested_animation_delay_ = base::TimeDelta();
                animation_task_.Run();
                animation_task_ = base::Closure();
                EXPECT_TRUE(did_request_next_frame_);
                did_request_next_frame_ = false;
                EXPECT_FALSE(did_request_redraw_);

                // After the scrollbar animation begins, we should start getting redraws.
                begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 4, fake_now);
                host_impl_->WillBeginImplFrame(begin_frame_args);
                host_impl_->Animate();
                EXPECT_TRUE(did_request_next_frame_);
                did_request_next_frame_ = false;
                EXPECT_TRUE(did_request_redraw_);
                did_request_redraw_ = false;
                EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
                EXPECT_TRUE(animation_task_.Equals(base::Closure()));
                host_impl_->DidFinishImplFrame();
            }

            // Setting the scroll offset outside a scroll should also cause the
            // scrollbar to appear and to schedule a scrollbar animation.
            if (host_impl_->active_tree()
                    ->property_trees()
                    ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                        host_impl_->InnerViewportScrollLayer()->id(),
                        gfx::ScrollOffset(5, 5)))
                host_impl_->active_tree()->DidUpdateScrollOffset(
                    host_impl_->InnerViewportScrollLayer()->id());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_FALSE(did_request_redraw_);
            if (expecting_animations) {
                EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
                    requested_animation_delay_);
                EXPECT_FALSE(animation_task_.Equals(base::Closure()));
                requested_animation_delay_ = base::TimeDelta();
                animation_task_ = base::Closure();
            } else {
                EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
                EXPECT_TRUE(animation_task_.Equals(base::Closure()));
            }

            if (expecting_animations) {
                // Scrolling should have stopped the animation, so we should not be
                // getting redraws.
                begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 5, fake_now);
                host_impl_->WillBeginImplFrame(begin_frame_args);
                host_impl_->Animate();
                EXPECT_FALSE(did_request_next_frame_);
                did_request_next_frame_ = false;
                EXPECT_FALSE(did_request_redraw_);
                did_request_redraw_ = false;
                host_impl_->DidFinishImplFrame();
            }

            // Scrollbar animation is not triggered unnecessarily.
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::WHEEL);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2dF(5, 0)).get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_TRUE(did_request_redraw_);
            did_request_redraw_ = false;
            EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
            EXPECT_TRUE(animation_task_.Equals(base::Closure()));

            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_FALSE(did_request_redraw_);
            EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
            EXPECT_TRUE(animation_task_.Equals(base::Closure()));

            // Changing page scale triggers scrollbar animation.
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 4.f);
            host_impl_->active_tree()->SetPageScaleOnActiveTree(1.1f);
            EXPECT_FALSE(did_request_next_frame_);
            EXPECT_FALSE(did_request_redraw_);
            if (expecting_animations) {
                EXPECT_EQ(base::TimeDelta::FromMilliseconds(20),
                    requested_animation_delay_);
                EXPECT_FALSE(animation_task_.Equals(base::Closure()));
                requested_animation_delay_ = base::TimeDelta();
                animation_task_ = base::Closure();
            } else {
                EXPECT_EQ(base::TimeDelta(), requested_animation_delay_);
                EXPECT_TRUE(animation_task_.Equals(base::Closure()));
            }
        }
    };

    TEST_F(LayerTreeHostImplTestScrollbarAnimation, LinearFade)
    {
        RunTest(LayerTreeSettings::LINEAR_FADE);
    }

    TEST_F(LayerTreeHostImplTestScrollbarAnimation, Thinning)
    {
        RunTest(LayerTreeSettings::THINNING);
    }

    TEST_F(LayerTreeHostImplTestScrollbarAnimation, NoAnimator)
    {
        RunTest(LayerTreeSettings::NO_ANIMATOR);
    }

    class LayerTreeHostImplTestScrollbarOpacity : public LayerTreeHostImplTest {
    protected:
        void RunTest(LayerTreeSettings::ScrollbarAnimator animator)
        {
            LayerTreeSettings settings = DefaultSettings();
            settings.scrollbar_animator = animator;
            settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20);
            settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(20);
            gfx::Size content_size(100, 100);

            // If no animator is set, scrollbar won't show and no animation is expected.
            bool expecting_animations = animator != LayerTreeSettings::NO_ANIMATOR;

            CreateHostImpl(settings, CreateCompositorFrameSink());
            host_impl_->CreatePendingTree();
            CreateScrollAndContentsLayers(host_impl_->pending_tree(), content_size);
            std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar = SolidColorScrollbarLayerImpl::Create(host_impl_->pending_tree(), 400,
                VERTICAL, 10, 0, false, true);
            scrollbar->test_properties()->opacity = 0.f;
            LayerImpl* scroll = host_impl_->pending_tree()->OuterViewportScrollLayer();
            LayerImpl* container = host_impl_->pending_tree()->InnerViewportContainerLayer();
            scrollbar->SetScrollLayerId(scroll->id());
            container->test_properties()->AddChild(std::move(scrollbar));
            host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
            host_impl_->pending_tree()->BuildPropertyTreesForTesting();
            host_impl_->ActivateSyncTree();

            LayerImpl* active_scrollbar_layer = host_impl_->active_tree()->LayerById(400);

            EffectNode* active_tree_node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
                active_scrollbar_layer->effect_tree_index());
            EXPECT_FLOAT_EQ(active_scrollbar_layer->Opacity(),
                active_tree_node->opacity);

            if (expecting_animations) {
                host_impl_->ScrollbarAnimationControllerForId(scroll->id())
                    ->DidMouseMoveNear(0);
            } else {
                EXPECT_EQ(nullptr,
                    host_impl_->ScrollbarAnimationControllerForId(scroll->id()));
            }
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::WHEEL);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2dF(0, 5)).get());
            host_impl_->ScrollEnd(EndState().get());
            host_impl_->CreatePendingTree();
            // To test the case where the effect tree index of scrollbar layer changes,
            // we force the container layer to create a render surface.
            container = host_impl_->pending_tree()->InnerViewportContainerLayer();
            container->test_properties()->force_render_surface = true;
            container->SetBounds(gfx::Size(10, 10));
            host_impl_->pending_tree()->BuildPropertyTreesForTesting();

            LayerImpl* pending_scrollbar_layer = host_impl_->pending_tree()->LayerById(400);
            pending_scrollbar_layer->SetNeedsPushProperties();
            EffectNode* pending_tree_node = host_impl_->pending_tree()->property_trees()->effect_tree.Node(
                pending_scrollbar_layer->effect_tree_index());
            host_impl_->pending_tree()
                ->property_trees()
                ->always_use_active_tree_opacity_effect_ids.push_back(400);
            if (expecting_animations) {
                EXPECT_FLOAT_EQ(1.f, active_tree_node->opacity);
                EXPECT_FLOAT_EQ(1.f, active_scrollbar_layer->Opacity());
            } else {
                EXPECT_FLOAT_EQ(0.f, active_tree_node->opacity);
                EXPECT_FLOAT_EQ(0.f, active_scrollbar_layer->Opacity());
            }
            EXPECT_FLOAT_EQ(0.f, pending_tree_node->opacity);
            host_impl_->ActivateSyncTree();
            active_tree_node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
                active_scrollbar_layer->effect_tree_index());
            if (expecting_animations) {
                EXPECT_FLOAT_EQ(1.f, active_tree_node->opacity);
                EXPECT_FLOAT_EQ(1.f, active_scrollbar_layer->Opacity());
            } else {
                EXPECT_FLOAT_EQ(0.f, active_tree_node->opacity);
                EXPECT_FLOAT_EQ(0.f, active_scrollbar_layer->Opacity());
            }
        }
    };

    TEST_F(LayerTreeHostImplTestScrollbarOpacity, LinearFade)
    {
        RunTest(LayerTreeSettings::LINEAR_FADE);
    }

    TEST_F(LayerTreeHostImplTestScrollbarOpacity, Thinning)
    {
        RunTest(LayerTreeSettings::THINNING);
    }

    TEST_F(LayerTreeHostImplTestScrollbarOpacity, NoAnimator)
    {
        RunTest(LayerTreeSettings::NO_ANIMATOR);
    }

    TEST_F(LayerTreeHostImplTest, ScrollbarInnerLargerThanOuter)
    {
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());

        gfx::Size inner_viewport_size(315, 200);
        gfx::Size outer_viewport_size(300, 200);
        gfx::Size content_size(1000, 1000);

        const int horiz_id = 11;
        const int child_clip_id = 14;
        const int child_scroll_id = 15;

        CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
        host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
            inner_viewport_size);
        host_impl_->active_tree()->OuterViewportContainerLayer()->SetBounds(
            outer_viewport_size);
        LayerImpl* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        std::unique_ptr<SolidColorScrollbarLayerImpl> horiz_scrollbar = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), horiz_id,
            HORIZONTAL, 5, 5, true, true);
        std::unique_ptr<LayerImpl> child = LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
        child->SetBounds(content_size);
        std::unique_ptr<LayerImpl> child_clip = LayerImpl::Create(host_impl_->active_tree(), child_clip_id);
        child->SetBounds(inner_viewport_size);

        horiz_scrollbar->SetScrollLayerId(root_scroll->id());

        EXPECT_EQ(300, horiz_scrollbar->clip_layer_length());
    }

    TEST_F(LayerTreeHostImplTest, ScrollbarRegistration)
    {
        LayerTreeSettings settings = DefaultSettings();
        settings.scrollbar_animator = LayerTreeSettings::LINEAR_FADE;
        settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(20);
        settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(20);
        CreateHostImpl(settings, CreateCompositorFrameSink());

        gfx::Size viewport_size(300, 200);
        gfx::Size content_size(1000, 1000);

        const int vert_1_id = 10;
        const int horiz_1_id = 11;
        const int vert_2_id = 12;
        const int horiz_2_id = 13;
        const int child_clip_id = 14;
        const int child_scroll_id = 15;

        CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
        host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
            viewport_size);
        LayerImpl* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        std::unique_ptr<SolidColorScrollbarLayerImpl> vert_1_scrollbar = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), vert_1_id,
            VERTICAL, 5, 5, true, true);
        std::unique_ptr<SolidColorScrollbarLayerImpl> horiz_1_scrollbar = SolidColorScrollbarLayerImpl::Create(
            host_impl_->active_tree(), horiz_1_id, HORIZONTAL, 5, 5, true, true);
        std::unique_ptr<SolidColorScrollbarLayerImpl> vert_2_scrollbar = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), vert_2_id,
            VERTICAL, 5, 5, true, true);
        std::unique_ptr<SolidColorScrollbarLayerImpl> horiz_2_scrollbar = SolidColorScrollbarLayerImpl::Create(
            host_impl_->active_tree(), horiz_2_id, HORIZONTAL, 5, 5, true, true);
        std::unique_ptr<LayerImpl> child = LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
        child->SetBounds(content_size);
        std::unique_ptr<LayerImpl> child_clip = LayerImpl::Create(host_impl_->active_tree(), child_clip_id);
        child->SetBounds(viewport_size);
        LayerImpl* child_ptr = child.get();
        LayerImpl* child_clip_ptr = child_clip.get();

        // Check scrollbar registration on the viewport layers.
        EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(root_scroll->id()).size());
        EXPECT_EQ(nullptr,
            host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));
        vert_1_scrollbar->SetScrollLayerId(root_scroll->id());
        EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(root_scroll->id()).size());
        EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));
        horiz_1_scrollbar->SetScrollLayerId(root_scroll->id());
        EXPECT_EQ(2ul, host_impl_->ScrollbarsFor(root_scroll->id()).size());
        EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));

        // Changing one of the viewport layers should result in a scrollbar animation
        // update.
        animation_task_ = base::Closure();
        host_impl_->active_tree()->InnerViewportContainerLayer()->SetBoundsDelta(
            gfx::Vector2dF(10, 10));
        EXPECT_FALSE(animation_task_.Equals(base::Closure()));
        animation_task_ = base::Closure();
        host_impl_->active_tree()->OuterViewportScrollLayer()->SetCurrentScrollOffset(
            gfx::ScrollOffset(10, 10));
        EXPECT_FALSE(animation_task_.Equals(base::Closure()));
        animation_task_ = base::Closure();
        host_impl_->active_tree()->InnerViewportScrollLayer()->SetCurrentScrollOffset(
            gfx::ScrollOffset(10, 10));
        EXPECT_FALSE(animation_task_.Equals(base::Closure()));
        animation_task_ = base::Closure();

        // Check scrollbar registration on a sublayer.
        child->SetScrollClipLayer(child_clip->id());
        child_clip->test_properties()->AddChild(std::move(child));
        root_scroll->test_properties()->AddChild(std::move(child_clip));
        EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(child_scroll_id).size());
        EXPECT_EQ(nullptr,
            host_impl_->ScrollbarAnimationControllerForId(child_scroll_id));
        vert_2_scrollbar->SetScrollLayerId(child_scroll_id);
        EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(child_scroll_id).size());
        EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForId(child_scroll_id));
        horiz_2_scrollbar->SetScrollLayerId(child_scroll_id);
        EXPECT_EQ(2ul, host_impl_->ScrollbarsFor(child_scroll_id).size());
        EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForId(child_scroll_id));

        // Changing one of the child layers should result in a scrollbar animation
        // update.
        animation_task_ = base::Closure();
        child_clip_ptr->SetBounds(gfx::Size(200, 200));
        EXPECT_FALSE(animation_task_.Equals(base::Closure()));
        animation_task_ = base::Closure();
        child_ptr->SetCurrentScrollOffset(gfx::ScrollOffset(10, 10));
        EXPECT_FALSE(animation_task_.Equals(base::Closure()));
        animation_task_ = base::Closure();

        // Check scrollbar unregistration.
        vert_1_scrollbar.reset();
        EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(root_scroll->id()).size());
        EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));
        horiz_1_scrollbar.reset();
        EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(root_scroll->id()).size());
        EXPECT_EQ(nullptr,
            host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));

        EXPECT_EQ(2ul, host_impl_->ScrollbarsFor(child_scroll_id).size());
        vert_2_scrollbar.reset();
        EXPECT_EQ(1ul, host_impl_->ScrollbarsFor(child_scroll_id).size());
        EXPECT_TRUE(host_impl_->ScrollbarAnimationControllerForId(child_scroll_id));
        horiz_2_scrollbar.reset();
        EXPECT_EQ(0ul, host_impl_->ScrollbarsFor(child_scroll_id).size());
        EXPECT_EQ(nullptr,
            host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));

        // Changing scroll offset should no longer trigger any animation.
        host_impl_->active_tree()->InnerViewportScrollLayer()->SetCurrentScrollOffset(
            gfx::ScrollOffset(20, 20));
        EXPECT_TRUE(animation_task_.Equals(base::Closure()));
        child_ptr->SetCurrentScrollOffset(gfx::ScrollOffset(20, 20));
        EXPECT_TRUE(animation_task_.Equals(base::Closure()));
    }

    void LayerTreeHostImplTest::SetupMouseMoveAtWithDeviceScale(
        float device_scale_factor)
    {
        LayerTreeSettings settings = DefaultSettings();
        settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500);
        settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300);
        settings.scrollbar_animator = LayerTreeSettings::THINNING;

        gfx::Size viewport_size(300, 200);
        gfx::Size device_viewport_size = gfx::ScaleToFlooredSize(viewport_size, device_scale_factor);
        gfx::Size content_size(1000, 1000);
        gfx::Size scrollbar_size(gfx::Size(15, viewport_size.height()));

        CreateHostImpl(settings, CreateCompositorFrameSink());
        host_impl_->active_tree()->SetDeviceScaleFactor(device_scale_factor);
        host_impl_->SetViewportSize(device_viewport_size);

        CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
        host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
            viewport_size);
        LayerImpl* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        // The scrollbar is on the left side.
        std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(), 6,
            VERTICAL, 5, 5, true, true);
        scrollbar->SetScrollLayerId(root_scroll->id());
        scrollbar->SetDrawsContent(true);
        scrollbar->SetBounds(scrollbar_size);
        scrollbar->SetTouchEventHandlerRegion(gfx::Rect(scrollbar_size));
        host_impl_->active_tree()
            ->InnerViewportContainerLayer()
            ->test_properties()
            ->AddChild(std::move(scrollbar));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        DrawFrame();
        host_impl_->active_tree()->UpdateDrawProperties(false);

        ScrollbarAnimationControllerThinning* scrollbar_animation_controller = static_cast<ScrollbarAnimationControllerThinning*>(
            host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));
        scrollbar_animation_controller->set_mouse_move_distance_for_test(100.f);

        host_impl_->MouseMoveAt(gfx::Point(200, 1));
        EXPECT_FALSE(scrollbar_animation_controller->mouse_is_near_scrollbar());

        host_impl_->MouseMoveAt(gfx::Point(100, 50));
        EXPECT_TRUE(scrollbar_animation_controller->mouse_is_near_scrollbar());

        host_impl_->MouseMoveAt(gfx::Point(116, 100));
        EXPECT_FALSE(scrollbar_animation_controller->mouse_is_near_scrollbar());

        scrollbar_animation_controller->set_mouse_move_distance_for_test(102.f);
        host_impl_->MouseMoveAt(gfx::Point(116, 100));
        EXPECT_TRUE(scrollbar_animation_controller->mouse_is_near_scrollbar());

        did_request_redraw_ = false;
        EXPECT_FALSE(scrollbar_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(10, 100));
        EXPECT_TRUE(scrollbar_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(10, 120));
        EXPECT_TRUE(scrollbar_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(150, 120));
        EXPECT_FALSE(scrollbar_animation_controller->mouse_is_over_scrollbar());
    }

    TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf1)
    {
        SetupMouseMoveAtWithDeviceScale(1.f);
    }

    TEST_F(LayerTreeHostImplTest, MouseMoveAtWithDeviceScaleOf2)
    {
        SetupMouseMoveAtWithDeviceScale(2.f);
    }

    TEST_F(LayerTreeHostImplTest, CompositorFrameMetadata)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
        DrawFrame();
        {
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_EQ(gfx::Vector2dF(), metadata.root_scroll_offset);
            EXPECT_EQ(1.f, metadata.page_scale_factor);
            EXPECT_EQ(gfx::SizeF(50.f, 50.f), metadata.scrollable_viewport_size);
            EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
            EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
            EXPECT_EQ(4.f, metadata.max_page_scale_factor);
            EXPECT_FALSE(metadata.root_overflow_x_hidden);
            EXPECT_FALSE(metadata.root_overflow_y_hidden);
        }

        // Scrolling should update metadata immediately.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        {
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
        }
        host_impl_->ScrollEnd(EndState().get());
        {
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
        }

        // Root "overflow: hidden" properties should be reflected on the outer
        // viewport scroll layer.
        {
            host_impl_->active_tree()
                ->OuterViewportScrollLayer()
                ->set_user_scrollable_horizontal(false);
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_TRUE(metadata.root_overflow_x_hidden);
            EXPECT_FALSE(metadata.root_overflow_y_hidden);

            host_impl_->active_tree()
                ->OuterViewportScrollLayer()
                ->set_user_scrollable_vertical(false);
            metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_TRUE(metadata.root_overflow_x_hidden);
            EXPECT_TRUE(metadata.root_overflow_y_hidden);
        }

        // Re-enable scrollability and verify that overflows are no longer hidden.
        {
            host_impl_->active_tree()
                ->OuterViewportScrollLayer()
                ->set_user_scrollable_horizontal(true);
            host_impl_->active_tree()
                ->OuterViewportScrollLayer()
                ->set_user_scrollable_vertical(true);
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_FALSE(metadata.root_overflow_x_hidden);
            EXPECT_FALSE(metadata.root_overflow_y_hidden);
        }

        // Root "overflow: hidden" properties should also be reflected on the
        // inner viewport scroll layer.
        {
            host_impl_->active_tree()
                ->InnerViewportScrollLayer()
                ->set_user_scrollable_horizontal(false);
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_TRUE(metadata.root_overflow_x_hidden);
            EXPECT_FALSE(metadata.root_overflow_y_hidden);

            host_impl_->active_tree()
                ->InnerViewportScrollLayer()
                ->set_user_scrollable_vertical(false);
            metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_TRUE(metadata.root_overflow_x_hidden);
            EXPECT_TRUE(metadata.root_overflow_y_hidden);
        }

        // Page scale should update metadata correctly (shrinking only the viewport).
        host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2.f, gfx::Point());
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());
        {
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
            EXPECT_EQ(2.f, metadata.page_scale_factor);
            EXPECT_EQ(gfx::SizeF(25.f, 25.f), metadata.scrollable_viewport_size);
            EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
            EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
            EXPECT_EQ(4.f, metadata.max_page_scale_factor);
        }

        // Likewise if set from the main thread.
        host_impl_->ProcessScrollDeltas();
        host_impl_->active_tree()->PushPageScaleFromMainThread(4.f, 0.5f, 4.f);
        host_impl_->active_tree()->SetPageScaleOnActiveTree(4.f);
        {
            CompositorFrameMetadata metadata = host_impl_->MakeCompositorFrameMetadata();
            EXPECT_EQ(gfx::Vector2dF(0.f, 10.f), metadata.root_scroll_offset);
            EXPECT_EQ(4.f, metadata.page_scale_factor);
            EXPECT_EQ(gfx::SizeF(12.5f, 12.5f), metadata.scrollable_viewport_size);
            EXPECT_EQ(gfx::SizeF(100.f, 100.f), metadata.root_layer_size);
            EXPECT_EQ(0.5f, metadata.min_page_scale_factor);
            EXPECT_EQ(4.f, metadata.max_page_scale_factor);
        }
    }

    class DidDrawCheckLayer : public LayerImpl {
    public:
        static std::unique_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id)
        {
            return base::WrapUnique(new DidDrawCheckLayer(tree_impl, id));
        }

        bool WillDraw(DrawMode draw_mode, ResourceProvider* provider) override
        {
            will_draw_called_ = true;
            if (will_draw_returns_false_)
                return false;
            return LayerImpl::WillDraw(draw_mode, provider);
        }

        void AppendQuads(RenderPass* render_pass,
            AppendQuadsData* append_quads_data) override
        {
            append_quads_called_ = true;
            LayerImpl::AppendQuads(render_pass, append_quads_data);
        }

        void DidDraw(ResourceProvider* provider) override
        {
            did_draw_called_ = true;
            LayerImpl::DidDraw(provider);
        }

        bool will_draw_called() const { return will_draw_called_; }
        bool append_quads_called() const { return append_quads_called_; }
        bool did_draw_called() const { return did_draw_called_; }

        void set_will_draw_returns_false() { will_draw_returns_false_ = true; }

        void ClearDidDrawCheck()
        {
            will_draw_called_ = false;
            append_quads_called_ = false;
            did_draw_called_ = false;
        }

        static void IgnoreResult(std::unique_ptr<CopyOutputResult> result) { }

        void AddCopyRequest()
        {
            test_properties()->copy_requests.push_back(
                CopyOutputRequest::CreateRequest(base::Bind(&IgnoreResult)));
        }

    protected:
        DidDrawCheckLayer(LayerTreeImpl* tree_impl, int id)
            : LayerImpl(tree_impl, id)
            , will_draw_returns_false_(false)
            , will_draw_called_(false)
            , append_quads_called_(false)
            , did_draw_called_(false)
        {
            SetBounds(gfx::Size(10, 10));
            SetDrawsContent(true);
            draw_properties().visible_layer_rect = gfx::Rect(0, 0, 10, 10);
        }

    private:
        bool will_draw_returns_false_;
        bool will_draw_called_;
        bool append_quads_called_;
        bool did_draw_called_;
    };

    TEST_F(LayerTreeHostImplTest, WillDrawReturningFalseDoesNotCall)
    {
        // The root layer is always drawn, so run this test on a child layer that
        // will be masked out by the root layer's bounds.
        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());

        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
        root->test_properties()->force_render_surface = true;
        DidDrawCheckLayer* layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        {
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            host_impl_->DrawLayers(&frame);
            host_impl_->DidDrawAllLayers(frame);

            EXPECT_TRUE(layer->will_draw_called());
            EXPECT_TRUE(layer->append_quads_called());
            EXPECT_TRUE(layer->did_draw_called());
        }

        host_impl_->SetViewportDamage(gfx::Rect(10, 10));

        {
            LayerTreeHostImpl::FrameData frame;

            layer->set_will_draw_returns_false();
            layer->ClearDidDrawCheck();

            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            host_impl_->DrawLayers(&frame);
            host_impl_->DidDrawAllLayers(frame);

            EXPECT_TRUE(layer->will_draw_called());
            EXPECT_FALSE(layer->append_quads_called());
            EXPECT_FALSE(layer->did_draw_called());
        }
    }

    TEST_F(LayerTreeHostImplTest, DidDrawNotCalledOnHiddenLayer)
    {
        // The root layer is always drawn, so run this test on a child layer that
        // will be masked out by the root layer's bounds.
        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
        root->SetMasksToBounds(true);
        root->test_properties()->force_render_surface = true;
        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
        DidDrawCheckLayer* layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);
        // Ensure visible_layer_rect for layer is empty.
        layer->SetPosition(gfx::PointF(100.f, 100.f));
        layer->SetBounds(gfx::Size(10, 10));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        LayerTreeHostImpl::FrameData frame;

        EXPECT_FALSE(layer->will_draw_called());
        EXPECT_FALSE(layer->did_draw_called());

        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        EXPECT_FALSE(layer->will_draw_called());
        EXPECT_FALSE(layer->did_draw_called());

        EXPECT_TRUE(layer->visible_layer_rect().IsEmpty());

        // Ensure visible_layer_rect for layer is not empty
        layer->SetPosition(gfx::PointF());
        layer->NoteLayerPropertyChanged();
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        EXPECT_FALSE(layer->will_draw_called());
        EXPECT_FALSE(layer->did_draw_called());

        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        EXPECT_TRUE(layer->will_draw_called());
        EXPECT_TRUE(layer->did_draw_called());

        EXPECT_FALSE(layer->visible_layer_rect().IsEmpty());
    }

    TEST_F(LayerTreeHostImplTest, WillDrawNotCalledOnOccludedLayer)
    {
        gfx::Size big_size(1000, 1000);
        host_impl_->SetViewportSize(big_size);

        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());

        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
        DidDrawCheckLayer* occluded_layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);

        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
        root->test_properties()->force_render_surface = true;
        DidDrawCheckLayer* top_layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children[1]);
        // This layer covers the occluded_layer above. Make this layer large so it can
        // occlude.
        top_layer->SetBounds(big_size);
        top_layer->SetContentsOpaque(true);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        LayerTreeHostImpl::FrameData frame;

        EXPECT_FALSE(occluded_layer->will_draw_called());
        EXPECT_FALSE(occluded_layer->did_draw_called());
        EXPECT_FALSE(top_layer->will_draw_called());
        EXPECT_FALSE(top_layer->did_draw_called());

        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        EXPECT_FALSE(occluded_layer->will_draw_called());
        EXPECT_FALSE(occluded_layer->did_draw_called());
        EXPECT_TRUE(top_layer->will_draw_called());
        EXPECT_TRUE(top_layer->did_draw_called());
    }

    TEST_F(LayerTreeHostImplTest, DidDrawCalledOnAllLayers)
    {
        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());

        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 2));
        root->test_properties()->force_render_surface = true;
        DidDrawCheckLayer* layer1 = static_cast<DidDrawCheckLayer*>(root->test_properties()->children[0]);

        layer1->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 3));
        DidDrawCheckLayer* layer2 = static_cast<DidDrawCheckLayer*>(layer1->test_properties()->children[0]);

        layer1->test_properties()->force_render_surface = true;
        layer1->test_properties()->should_flatten_transform = true;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        EXPECT_FALSE(root->did_draw_called());
        EXPECT_FALSE(layer1->did_draw_called());
        EXPECT_FALSE(layer2->did_draw_called());

        LayerTreeHostImpl::FrameData frame;
        FakeLayerTreeHostImpl::RecursiveUpdateNumChildren(
            *host_impl_->active_tree()->begin());
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        EXPECT_TRUE(root->did_draw_called());
        EXPECT_TRUE(layer1->did_draw_called());
        EXPECT_TRUE(layer2->did_draw_called());

        EXPECT_NE(root->render_surface(), layer1->render_surface());
        EXPECT_TRUE(layer1->render_surface());
    }

    class MissingTextureAnimatingLayer : public DidDrawCheckLayer {
    public:
        static std::unique_ptr<LayerImpl> Create(
            LayerTreeImpl* tree_impl,
            int id,
            bool tile_missing,
            bool had_incomplete_tile,
            bool animating,
            ResourceProvider* resource_provider,
            scoped_refptr<AnimationTimeline> timeline)
        {
            return base::WrapUnique(new MissingTextureAnimatingLayer(
                tree_impl, id, tile_missing, had_incomplete_tile, animating,
                resource_provider, timeline));
        }

        void AppendQuads(RenderPass* render_pass,
            AppendQuadsData* append_quads_data) override
        {
            LayerImpl::AppendQuads(render_pass, append_quads_data);
            if (had_incomplete_tile_)
                append_quads_data->num_incomplete_tiles++;
            if (tile_missing_)
                append_quads_data->num_missing_tiles++;
        }

    private:
        MissingTextureAnimatingLayer(LayerTreeImpl* tree_impl,
            int id,
            bool tile_missing,
            bool had_incomplete_tile,
            bool animating,
            ResourceProvider* resource_provider,
            scoped_refptr<AnimationTimeline> timeline)
            : DidDrawCheckLayer(tree_impl, id)
            , tile_missing_(tile_missing)
            , had_incomplete_tile_(had_incomplete_tile)
        {
            if (animating) {
                this->SetElementId(LayerIdToElementIdForTesting(id));
                AddAnimatedTransformToElementWithPlayer(this->element_id(), timeline,
                    10.0, 3, 0);
            }
        }

        bool tile_missing_;
        bool had_incomplete_tile_;
    };

    struct PrepareToDrawSuccessTestCase {
        struct State {
            bool has_missing_tile = false;
            bool has_incomplete_tile = false;
            bool is_animating = false;
            bool has_copy_request = false;
        };
        bool high_res_required = false;
        State layer_before;
        State layer_between;
        State layer_after;
        DrawResult expected_result;

        explicit PrepareToDrawSuccessTestCase(DrawResult result)
            : expected_result(result)
        {
        }
    };

    static void CreateLayerFromState(
        DidDrawCheckLayer* root,
        const scoped_refptr<AnimationTimeline>& timeline,
        const PrepareToDrawSuccessTestCase::State& state)
    {
        static int layer_id = 2;
        root->test_properties()->AddChild(MissingTextureAnimatingLayer::Create(
            root->layer_tree_impl(), layer_id++, state.has_missing_tile,
            state.has_incomplete_tile, state.is_animating,
            root->layer_tree_impl()->resource_provider(), timeline));
        DidDrawCheckLayer* layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children.back());
        if (state.has_copy_request)
            layer->AddCopyRequest();
    }

    TEST_F(LayerTreeHostImplTest, PrepareToDrawSucceedsAndFails)
    {
        std::vector<PrepareToDrawSuccessTestCase> cases;

        // 0. Default case.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        // 1. Animated layer first.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_before.is_animating = true;
        // 2. Animated layer between.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_between.is_animating = true;
        // 3. Animated layer last.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_after.is_animating = true;
        // 4. Missing tile first.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_before.has_missing_tile = true;
        // 5. Missing tile between.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_between.has_missing_tile = true;
        // 6. Missing tile last.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_after.has_missing_tile = true;
        // 7. Incomplete tile first.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_before.has_incomplete_tile = true;
        // 8. Incomplete tile between.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_between.has_incomplete_tile = true;
        // 9. Incomplete tile last.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_after.has_incomplete_tile = true;
        // 10. Animation with missing tile.
        cases.push_back(
            PrepareToDrawSuccessTestCase(DRAW_ABORTED_CHECKERBOARD_ANIMATIONS));
        cases.back().layer_between.has_missing_tile = true;
        cases.back().layer_between.is_animating = true;
        // 11. Animation with incomplete tile.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_between.has_incomplete_tile = true;
        cases.back().layer_between.is_animating = true;

        // 12. High res required.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().high_res_required = true;
        // 13. High res required with incomplete tile.
        cases.push_back(
            PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
        cases.back().high_res_required = true;
        cases.back().layer_between.has_incomplete_tile = true;
        // 14. High res required with missing tile.
        cases.push_back(
            PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
        cases.back().high_res_required = true;
        cases.back().layer_between.has_missing_tile = true;

        // 15. High res required is higher priority than animating missing tiles.
        cases.push_back(
            PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
        cases.back().high_res_required = true;
        cases.back().layer_between.has_missing_tile = true;
        cases.back().layer_after.has_missing_tile = true;
        cases.back().layer_after.is_animating = true;
        // 16. High res required is higher priority than animating missing tiles.
        cases.push_back(
            PrepareToDrawSuccessTestCase(DRAW_ABORTED_MISSING_HIGH_RES_CONTENT));
        cases.back().high_res_required = true;
        cases.back().layer_between.has_missing_tile = true;
        cases.back().layer_before.has_missing_tile = true;
        cases.back().layer_before.is_animating = true;

        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());
        root->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        for (size_t i = 0; i < cases.size(); ++i) {
            // Clean up host_impl_ state.
            const auto& testcase = cases[i];
            std::vector<LayerImpl*> to_remove;
            for (auto* child : root->test_properties()->children)
                to_remove.push_back(child);
            for (auto* child : to_remove)
                root->test_properties()->RemoveChild(child);
            timeline()->ClearPlayers();

            std::ostringstream scope;
            scope << "Test case: " << i;
            SCOPED_TRACE(scope.str());

            CreateLayerFromState(root, timeline(), testcase.layer_before);
            CreateLayerFromState(root, timeline(), testcase.layer_between);
            CreateLayerFromState(root, timeline(), testcase.layer_after);
            host_impl_->active_tree()->BuildPropertyTreesForTesting();

            if (testcase.high_res_required)
                host_impl_->SetRequiresHighResToDraw();

            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(testcase.expected_result, host_impl_->PrepareToDraw(&frame));
            host_impl_->DrawLayers(&frame);
            host_impl_->DidDrawAllLayers(frame);
        }
    }

    TEST_F(LayerTreeHostImplTest,
        PrepareToDrawWhenDrawAndSwapFullViewportEveryFrame)
    {
        CreateHostImpl(DefaultSettings(), FakeCompositorFrameSink::CreateSoftware());

        const gfx::Transform external_transform;
        const gfx::Rect external_viewport;
        const bool resourceless_software_draw = true;
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        std::vector<PrepareToDrawSuccessTestCase> cases;

        // 0. Default case.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        // 1. Animation with missing tile.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().layer_between.has_missing_tile = true;
        cases.back().layer_between.is_animating = true;
        // 2. High res required with incomplete tile.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().high_res_required = true;
        cases.back().layer_between.has_incomplete_tile = true;
        // 3. High res required with missing tile.
        cases.push_back(PrepareToDrawSuccessTestCase(DRAW_SUCCESS));
        cases.back().high_res_required = true;
        cases.back().layer_between.has_missing_tile = true;

        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), 1));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(
            host_impl_->active_tree()->root_layer_for_testing());
        root->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);

        for (size_t i = 0; i < cases.size(); ++i) {
            const auto& testcase = cases[i];
            std::vector<LayerImpl*> to_remove;
            for (auto* child : root->test_properties()->children)
                to_remove.push_back(child);
            for (auto* child : to_remove)
                root->test_properties()->RemoveChild(child);

            std::ostringstream scope;
            scope << "Test case: " << i;
            SCOPED_TRACE(scope.str());

            CreateLayerFromState(root, timeline(), testcase.layer_before);
            CreateLayerFromState(root, timeline(), testcase.layer_between);
            CreateLayerFromState(root, timeline(), testcase.layer_after);
            host_impl_->active_tree()->BuildPropertyTreesForTesting();

            if (testcase.high_res_required)
                host_impl_->SetRequiresHighResToDraw();

            host_impl_->OnDraw(external_transform, external_viewport,
                resourceless_software_draw);
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollRootIgnored)
    {
        std::unique_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
        root->SetScrollClipLayer(Layer::INVALID_ID);
        root->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        // Scroll event is ignored because layer is not scrollable.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, ClampingAfterActivation)
    {
        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
        CreateScrollAndContentsLayers(host_impl_->pending_tree(),
            gfx::Size(100, 100));
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();
        host_impl_->ActivateSyncTree();

        host_impl_->CreatePendingTree();
        const gfx::ScrollOffset pending_scroll = gfx::ScrollOffset(-100, -100);
        LayerImpl* active_outer_layer = host_impl_->active_tree()->OuterViewportScrollLayer();
        LayerImpl* pending_outer_layer = host_impl_->pending_tree()->OuterViewportScrollLayer();
        pending_outer_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(pending_outer_layer->id(),
                pending_scroll);

        host_impl_->ActivateSyncTree();
        // Scrolloffsets on the active tree will be clamped after activation.
        EXPECT_EQ(active_outer_layer->CurrentScrollOffset(), gfx::ScrollOffset(0, 0));
    }

    class LayerTreeHostImplBrowserControlsTest : public LayerTreeHostImplTest {
    public:
        LayerTreeHostImplBrowserControlsTest()
            // Make the clip size the same as the layer (content) size so the layer is
            // non-scrollable.
            : layer_size_(10, 10)
            , clip_size_(layer_size_)
            , top_controls_height_(50)
        {
            viewport_size_ = gfx::Size(clip_size_.width(),
                clip_size_.height() + top_controls_height_);
        }

        bool CreateHostImpl(
            const LayerTreeSettings& settings,
            std::unique_ptr<CompositorFrameSink> compositor_frame_sink) override
        {
            bool init = LayerTreeHostImplTest::CreateHostImpl(
                settings, std::move(compositor_frame_sink));
            if (init) {
                host_impl_->active_tree()->set_top_controls_height(top_controls_height_);
                host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f);
                host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
            }
            return init;
        }

        void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            const gfx::Size& inner_viewport_size,
            const gfx::Size& outer_viewport_size,
            const gfx::Size& scroll_layer_size)
        {
            settings_ = DefaultSettings();
            CreateHostImpl(settings_, CreateCompositorFrameSink());
            SetupBrowserControlsAndScrollLayerWithVirtualViewport(
                host_impl_->active_tree(), inner_viewport_size, outer_viewport_size,
                scroll_layer_size);
        }

        void SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            LayerTreeImpl* tree_impl,
            const gfx::Size& inner_viewport_size,
            const gfx::Size& outer_viewport_size,
            const gfx::Size& scroll_layer_size)
        {
            tree_impl->set_browser_controls_shrink_blink_size(true);
            tree_impl->set_top_controls_height(top_controls_height_);
            tree_impl->SetCurrentBrowserControlsShownRatio(1.f);
            tree_impl->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
            host_impl_->DidChangeBrowserControlsPosition();

            std::unique_ptr<LayerImpl> root = LayerImpl::Create(tree_impl, 1);
            std::unique_ptr<LayerImpl> root_clip = LayerImpl::Create(tree_impl, 2);
            std::unique_ptr<LayerImpl> page_scale = LayerImpl::Create(tree_impl, 3);

            std::unique_ptr<LayerImpl> outer_scroll = LayerImpl::Create(tree_impl, 4);
            std::unique_ptr<LayerImpl> outer_clip = LayerImpl::Create(tree_impl, 5);

            root_clip->SetBounds(inner_viewport_size);
            root->SetScrollClipLayer(root_clip->id());
            root->SetBounds(outer_viewport_size);
            root->SetPosition(gfx::PointF());
            root->SetDrawsContent(false);
            root_clip->test_properties()->force_render_surface = true;
            root->test_properties()->is_container_for_fixed_position_layers = true;
            outer_clip->SetBounds(outer_viewport_size);
            outer_scroll->SetScrollClipLayer(outer_clip->id());
            outer_scroll->SetBounds(scroll_layer_size);
            outer_scroll->SetPosition(gfx::PointF());
            outer_scroll->SetDrawsContent(false);
            outer_scroll->test_properties()->is_container_for_fixed_position_layers = true;

            int inner_viewport_scroll_layer_id = root->id();
            int outer_viewport_scroll_layer_id = outer_scroll->id();
            int page_scale_layer_id = page_scale->id();

            outer_clip->test_properties()->AddChild(std::move(outer_scroll));
            root->test_properties()->AddChild(std::move(outer_clip));
            page_scale->test_properties()->AddChild(std::move(root));
            root_clip->test_properties()->AddChild(std::move(page_scale));

            tree_impl->SetRootLayerForTesting(std::move(root_clip));
            tree_impl->SetViewportLayersFromIds(Layer::INVALID_ID, page_scale_layer_id,
                inner_viewport_scroll_layer_id,
                outer_viewport_scroll_layer_id);
            tree_impl->BuildPropertyTreesForTesting();

            host_impl_->SetViewportSize(inner_viewport_size);
            LayerImpl* root_clip_ptr = tree_impl->root_layer_for_testing();
            EXPECT_EQ(inner_viewport_size, root_clip_ptr->bounds());
        }

    protected:
        gfx::Size layer_size_;
        gfx::Size clip_size_;
        gfx::Size viewport_size_;
        float top_controls_height_;

        LayerTreeSettings settings_;
    }; // class LayerTreeHostImplBrowserControlsTest

    // Tests that, on a page with content the same size as the viewport, hiding
    // the browser controls also increases the ScrollableSize (i.e. the content
    // size). Since the viewport got larger, the effective scrollable "content" also
    // did. This ensures, for one thing, that the overscroll glow is shown in the
    // right place.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        HidingBrowserControlsExpandsScrollableSize)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(50, 50), gfx::Size(50, 50), gfx::Size(50, 50));

        LayerTreeImpl* active_tree = host_impl_->active_tree();

        // Create a content layer beneath the outer viewport scroll layer.
        int id = host_impl_->OuterViewportScrollLayer()->id();
        host_impl_->OuterViewportScrollLayer()->test_properties()->AddChild(
            LayerImpl::Create(host_impl_->active_tree(), id + 2));
        LayerImpl* content = active_tree->OuterViewportScrollLayer()->test_properties()->children[0];
        content->SetBounds(gfx::Size(50, 50));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        LayerImpl* inner_container = active_tree->InnerViewportContainerLayer();
        LayerImpl* outer_container = active_tree->OuterViewportContainerLayer();

        // The browser controls should start off showing so the viewport should be
        // shrunk.
        ASSERT_EQ(gfx::Size(50, 50), inner_container->bounds());
        ASSERT_EQ(gfx::Size(50, 50), outer_container->bounds());

        EXPECT_EQ(gfx::SizeF(50, 50), active_tree->ScrollableSize());

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);

        host_impl_->browser_controls_manager()->ScrollBegin();

        // Hide the browser controls by a bit, the scrollable size should increase but
        // the actual content bounds shouldn't.
        {
            host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0.f, 25.f));
            ASSERT_EQ(gfx::Size(50, 75), inner_container->bounds());
            ASSERT_EQ(gfx::Size(50, 75), outer_container->bounds());
            EXPECT_EQ(gfx::SizeF(50, 75), active_tree->ScrollableSize());
            EXPECT_EQ(gfx::SizeF(50, 50), content->BoundsForScrolling());
        }

        // Fully hide the browser controls.
        {
            host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0.f, 25.f));
            ASSERT_EQ(gfx::Size(50, 100), inner_container->bounds());
            ASSERT_EQ(gfx::Size(50, 100), outer_container->bounds());
            EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize());
            EXPECT_EQ(gfx::SizeF(50, 50), content->BoundsForScrolling());
        }

        // Scrolling additionally shouldn't have any effect.
        {
            host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0.f, 25.f));
            ASSERT_EQ(gfx::Size(50, 100), inner_container->bounds());
            ASSERT_EQ(gfx::Size(50, 100), outer_container->bounds());
            EXPECT_EQ(gfx::SizeF(50, 100), active_tree->ScrollableSize());
            EXPECT_EQ(gfx::SizeF(50, 50), content->BoundsForScrolling());
        }

        host_impl_->browser_controls_manager()->ScrollEnd();
        host_impl_->ScrollEnd(EndState().get());
    }

    TEST_F(LayerTreeHostImplBrowserControlsTest,
        ScrollBrowserControlsByFractionalAmount)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(10, 10), gfx::Size(10, 10), gfx::Size(10, 10));
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);

        // Make the test scroll delta a fractional amount, to verify that the
        // fixed container size delta is (1) non-zero, and (2) fractional, and
        // (3) matches the movement of the browser controls.
        gfx::Vector2dF top_controls_scroll_delta(0.f, 5.25f);
        host_impl_->browser_controls_manager()->ScrollBegin();
        host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
        host_impl_->browser_controls_manager()->ScrollEnd();

        LayerImpl* inner_viewport_scroll_layer = host_impl_->active_tree()->InnerViewportScrollLayer();
        DCHECK(inner_viewport_scroll_layer);
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(),
            inner_viewport_scroll_layer->FixedContainerSizeDelta().y());
    }

    // In this test, the outer viewport is initially unscrollable. We test that a
    // scroll initiated on the inner viewport, causing the browser controls to show
    // and thus making the outer viewport scrollable, still scrolls the outer
    // viewport.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        BrowserControlsOuterViewportBecomesScrollable)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100));
        DrawFrame();

        LayerImpl* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
        inner_scroll->SetDrawsContent(true);
        LayerImpl* inner_container = host_impl_->active_tree()->InnerViewportContainerLayer();
        LayerImpl* outer_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        outer_scroll->SetDrawsContent(true);
        LayerImpl* outer_container = host_impl_->active_tree()->OuterViewportContainerLayer();

        // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
        outer_scroll->SetDrawsContent(true);
        host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 1.f, 2.f);

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0.f, 50.f)).get());

        // The entire scroll delta should have been used to hide the browser controls.
        // The viewport layers should be resized back to their full sizes.
        EXPECT_EQ(0.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
        EXPECT_EQ(0.f, inner_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(100.f, inner_container->BoundsForScrolling().height());
        EXPECT_EQ(100.f, outer_container->BoundsForScrolling().height());

        // The inner viewport should be scrollable by 50px * page_scale.
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0.f, 100.f)).get());
        EXPECT_EQ(50.f, inner_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(0.f, outer_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(gfx::ScrollOffset(), outer_scroll->MaxScrollOffset());

        host_impl_->ScrollEnd(EndState().get());

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), outer_scroll);

        host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0.f, -50.f)).get());

        // The entire scroll delta should have been used to show the browser controls.
        // The outer viewport should be resized to accomodate and scrolled to the
        // bottom of the document to keep the viewport in place.
        EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
        EXPECT_EQ(50.f, outer_container->BoundsForScrolling().height());
        EXPECT_EQ(50.f, inner_container->BoundsForScrolling().height());
        EXPECT_EQ(25.f, outer_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(25.f, inner_scroll->CurrentScrollOffset().y());

        // Now when we continue scrolling, make sure the outer viewport gets scrolled
        // since it wasn't scrollable when the scroll began.
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0.f, -20.f)).get());
        EXPECT_EQ(25.f, outer_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(15.f, inner_scroll->CurrentScrollOffset().y());

        host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0.f, -30.f)).get());
        EXPECT_EQ(25.f, outer_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(0.f, inner_scroll->CurrentScrollOffset().y());

        host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0.f, -50.f)).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_EQ(0.f, outer_scroll->CurrentScrollOffset().y());
        EXPECT_EQ(0.f, inner_scroll->CurrentScrollOffset().y());
    }

    // Test that the fixed position container delta is appropriately adjusted
    // by the browser controls showing/hiding and page scale doesn't affect it.
    TEST_F(LayerTreeHostImplBrowserControlsTest, FixedContainerDelta)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(100, 100), gfx::Size(100, 100), gfx::Size(100, 100));
        DrawFrame();
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);

        float page_scale = 1.5f;
        LayerImpl* outer_viewport_scroll_layer = host_impl_->active_tree()->OuterViewportScrollLayer();

        // Zoom in, since the fixed container is the outer viewport, the delta should
        // not be scaled.
        host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f, 2.f);

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);

        // Scroll down, the browser controls hiding should expand the viewport size so
        // the delta should be equal to the scroll distance.
        gfx::Vector2dF top_controls_scroll_delta(0.f, 20.f);
        host_impl_->browser_controls_manager()->ScrollBegin();
        host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
        EXPECT_FLOAT_EQ(top_controls_height_ - top_controls_scroll_delta.y(),
            host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_FLOAT_EQ(top_controls_scroll_delta.y(),
            outer_viewport_scroll_layer->FixedContainerSizeDelta().y());
        host_impl_->ScrollEnd(EndState().get());

        // Scroll past the maximum extent. The delta shouldn't be greater than the
        // browser controls height.
        host_impl_->browser_controls_manager()->ScrollBegin();
        host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
        host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
        host_impl_->browser_controls_manager()->ScrollBy(top_controls_scroll_delta);
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, top_controls_height_),
            outer_viewport_scroll_layer->FixedContainerSizeDelta());
        host_impl_->ScrollEnd(EndState().get());

        // Scroll in the direction to make the browser controls show.
        host_impl_->browser_controls_manager()->ScrollBegin();
        host_impl_->browser_controls_manager()->ScrollBy(-top_controls_scroll_delta);
        EXPECT_EQ(top_controls_scroll_delta.y(),
            host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_VECTOR_EQ(
            gfx::Vector2dF(0, top_controls_height_ - top_controls_scroll_delta.y()),
            outer_viewport_scroll_layer->FixedContainerSizeDelta());
        host_impl_->browser_controls_manager()->ScrollEnd();
    }

    // Push a browser controls ratio from the main thread that we didn't send as a
    // delta and make sure that the ratio is clamped to the [0, 1] range.
    TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsPushUnsentRatio)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(10, 50), gfx::Size(10, 50), gfx::Size(10, 100));
        DrawFrame();

        // Need SetDrawsContent so ScrollBegin's hit test finds an actual layer.
        LayerImpl* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
        inner_scroll->SetDrawsContent(true);
        LayerImpl* outer_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        outer_scroll->SetDrawsContent(true);

        host_impl_->active_tree()->PushBrowserControlsFromMainThread(1);
        ASSERT_EQ(1.0f,
            host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.5f);
        ASSERT_EQ(0.5f,
            host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        host_impl_->active_tree()->PushBrowserControlsFromMainThread(0);

        ASSERT_EQ(0, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
    }

    // Test that if only the browser controls are scrolled, we shouldn't request a
    // commit.
    TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsDontTriggerCommit)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(100, 50), gfx::Size(100, 100), gfx::Size(100, 100));
        DrawFrame();

        // Show browser controls
        EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        // Scroll 25px to hide browser controls
        gfx::Vector2dF scroll_delta(0.f, 25.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        EXPECT_FALSE(did_request_commit_);
    }

    // Test that if a scrollable sublayer doesn't consume the scroll,
    // browser controls should hide when scrolling down.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        BrowserControlsScrollableSublayer)
    {
        gfx::Size sub_content_size(100, 400);
        gfx::Size sub_content_layer_size(100, 300);
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(100, 50), gfx::Size(100, 100), gfx::Size(100, 100));
        DrawFrame();

        // Show browser controls
        EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        LayerImpl* outer_viewport_scroll_layer = host_impl_->active_tree()->OuterViewportScrollLayer();
        int id = outer_viewport_scroll_layer->id();

        std::unique_ptr<LayerImpl> child = LayerImpl::Create(host_impl_->active_tree(), id + 2);
        std::unique_ptr<LayerImpl> child_clip = LayerImpl::Create(host_impl_->active_tree(), id + 3);

        child_clip->SetBounds(sub_content_layer_size);
        child->SetScrollClipLayer(child_clip->id());
        child->SetBounds(sub_content_size);
        child->SetPosition(gfx::PointF());
        child->SetDrawsContent(true);
        child->test_properties()->is_container_for_fixed_position_layers = true;

        // scroll child to limit
        SetScrollOffsetDelta(child.get(), gfx::Vector2dF(0, 100.f));
        child_clip->test_properties()->AddChild(std::move(child));
        outer_viewport_scroll_layer->test_properties()->AddChild(
            std::move(child_clip));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        // Scroll 25px to hide browser controls
        gfx::Vector2dF scroll_delta(0.f, 25.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // Browser controls should be hidden
        EXPECT_EQ(scroll_delta.y(),
            top_controls_height_ - host_impl_->browser_controls_manager()->ContentTopOffset());
    }

    // Ensure setting the browser controls position explicitly using the setters on
    // the TreeImpl correctly affects the browser controls manager and viewport
    // bounds.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        PositionBrowserControlsExplicitly)
    {
        settings_ = DefaultSettings();
        CreateHostImpl(settings_, CreateCompositorFrameSink());
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            layer_size_, layer_size_, layer_size_);
        DrawFrame();

        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.f);
        host_impl_->active_tree()->top_controls_shown_ratio()->PushFromMainThread(
            30.f / top_controls_height_);
        host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive();
        EXPECT_FLOAT_EQ(30.f,
            host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_FLOAT_EQ(-20.f,
            host_impl_->browser_controls_manager()->ControlsTopOffset());

        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.f);
        EXPECT_FLOAT_EQ(0.f,
            host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_FLOAT_EQ(-50.f,
            host_impl_->browser_controls_manager()->ControlsTopOffset());

        host_impl_->DidChangeBrowserControlsPosition();

        // Now that browser controls have moved, expect the clip to resize.
        LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
                                        ->test_properties()
                                        ->parent->test_properties()
                                        ->parent;
        EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
    }

    // Test that the top_controls delta and sent delta are appropriately
    // applied on sync tree activation. The total browser controls offset shouldn't
    // change after the activation.
    TEST_F(LayerTreeHostImplBrowserControlsTest, ApplyDeltaOnTreeActivation)
    {
        settings_ = DefaultSettings();
        CreateHostImpl(settings_, CreateCompositorFrameSink());
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            layer_size_, layer_size_, layer_size_);
        DrawFrame();

        host_impl_->active_tree()->top_controls_shown_ratio()->PushFromMainThread(
            20.f / top_controls_height_);
        host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive();
        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(
            15.f / top_controls_height_);
        host_impl_->active_tree()
            ->top_controls_shown_ratio()
            ->PullDeltaForMainThread();
        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.f);
        host_impl_->sync_tree()->PushBrowserControlsFromMainThread(
            15.f / top_controls_height_);

        host_impl_->DidChangeBrowserControlsPosition();
        LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
                                        ->test_properties()
                                        ->parent->test_properties()
                                        ->parent;
        EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());

        host_impl_->ActivateSyncTree();

        inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
                             ->test_properties()
                             ->parent->test_properties()
                             ->parent;
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());

        EXPECT_FLOAT_EQ(
            -15.f, host_impl_->active_tree()->top_controls_shown_ratio()->Delta() * top_controls_height_);
        EXPECT_FLOAT_EQ(
            15.f,
            host_impl_->active_tree()->top_controls_shown_ratio()->ActiveBase() * top_controls_height_);
    }

    // Test that changing the browser controls layout height is correctly applied to
    // the inner viewport container bounds. That is, the browser controls layout
    // height is the amount that the inner viewport container was shrunk outside
    // the compositor to accommodate the browser controls.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        BrowserControlsLayoutHeightChanged)
    {
        settings_ = DefaultSettings();
        CreateHostImpl(settings_, CreateCompositorFrameSink());
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            layer_size_, layer_size_, layer_size_);
        DrawFrame();

        host_impl_->sync_tree()->PushBrowserControlsFromMainThread(1.f);
        host_impl_->sync_tree()->set_browser_controls_shrink_blink_size(true);

        host_impl_->active_tree()->top_controls_shown_ratio()->PushFromMainThread(
            1.f);
        host_impl_->active_tree()->top_controls_shown_ratio()->PushPendingToActive();
        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(0.f);

        host_impl_->DidChangeBrowserControlsPosition();
        LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
                                        ->test_properties()
                                        ->parent->test_properties()
                                        ->parent;
        EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());

        host_impl_->sync_tree()->root_layer_for_testing()->SetBounds(
            gfx::Size(inner_clip_ptr->bounds().width(),
                inner_clip_ptr->bounds().height() - 50.f));

        host_impl_->ActivateSyncTree();

        inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
                             ->test_properties()
                             ->parent->test_properties()
                             ->parent;
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());

        // The total bounds should remain unchanged since the bounds delta should
        // account for the difference between the layout height and the current
        // browser controls offset.
        EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 50.f), inner_clip_ptr->bounds_delta());

        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f);
        host_impl_->DidChangeBrowserControlsPosition();

        EXPECT_EQ(1.f,
            host_impl_->browser_controls_manager()->TopControlsShownRatio());
        EXPECT_EQ(50.f, host_impl_->browser_controls_manager()->TopControlsHeight());
        EXPECT_EQ(50.f, host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 0.f), inner_clip_ptr->bounds_delta());
        EXPECT_EQ(gfx::Size(viewport_size_.width(), viewport_size_.height() - 50.f),
            inner_clip_ptr->bounds());
    }

    // Test that showing/hiding the browser controls when the viewport is fully
    // scrolled doesn't incorrectly change the viewport offset due to clamping from
    // changing viewport bounds.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        BrowserControlsViewportOffsetClamping)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
        DrawFrame();

        EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();

        // Scroll the viewports to max scroll offset.
        SetScrollOffsetDelta(outer_scroll, gfx::Vector2dF(0, 200.f));
        SetScrollOffsetDelta(inner_scroll, gfx::Vector2dF(100, 100.f));

        gfx::ScrollOffset viewport_offset = host_impl_->active_tree()->TotalScrollOffset();
        EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(), viewport_offset);

        // Hide the browser controls by 25px.
        gfx::Vector2dF scroll_delta(0.f, 25.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

        // scrolling down at the max extents no longer hides the browser controls
        EXPECT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        // forcefully hide the browser controls by 25px
        host_impl_->browser_controls_manager()->ScrollBy(scroll_delta);
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_FLOAT_EQ(
            scroll_delta.y(),
            top_controls_height_ - host_impl_->browser_controls_manager()->ContentTopOffset());

        inner_scroll->ClampScrollToMaxScrollOffset();
        outer_scroll->ClampScrollToMaxScrollOffset();

        // We should still be fully scrolled.
        EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(),
            host_impl_->active_tree()->TotalScrollOffset());

        viewport_offset = host_impl_->active_tree()->TotalScrollOffset();

        // Bring the browser controls down by 25px.
        scroll_delta = gfx::Vector2dF(0.f, -25.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // The viewport offset shouldn't have changed.
        EXPECT_EQ(viewport_offset, host_impl_->active_tree()->TotalScrollOffset());

        // Scroll the viewports to max scroll offset.
        SetScrollOffsetDelta(outer_scroll, gfx::Vector2dF(0, 200.f));
        SetScrollOffsetDelta(inner_scroll, gfx::Vector2dF(100, 100.f));
        EXPECT_EQ(host_impl_->active_tree()->TotalMaxScrollOffset(),
            host_impl_->active_tree()->TotalScrollOffset());
    }

    // Test that the browser controls coming in and out maintains the same aspect
    // ratio between the inner and outer viewports.
    TEST_F(LayerTreeHostImplBrowserControlsTest, BrowserControlsAspectRatio)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 2.f);
        DrawFrame();

        EXPECT_FLOAT_EQ(top_controls_height_,
            host_impl_->browser_controls_manager()->ContentTopOffset());

        gfx::Vector2dF scroll_delta(0.f, 25.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_FLOAT_EQ(
            scroll_delta.y(),
            top_controls_height_ - host_impl_->browser_controls_manager()->ContentTopOffset());

        // Browser controls were hidden by 25px so the inner viewport should have
        // expanded by that much.
        LayerImpl* outer_container = host_impl_->active_tree()->OuterViewportContainerLayer();
        LayerImpl* inner_container = host_impl_->active_tree()->InnerViewportContainerLayer();
        EXPECT_EQ(gfx::SizeF(100.f, 100.f + 25.f),
            inner_container->BoundsForScrolling());

        // Outer viewport should match inner's aspect ratio. The bounds are ceiled.
        float aspect_ratio = inner_container->BoundsForScrolling().width() / inner_container->BoundsForScrolling().height();
        gfx::SizeF expected = gfx::SizeF(gfx::ToCeiledSize(gfx::SizeF(200, 200 / aspect_ratio)));
        EXPECT_EQ(expected, outer_container->BoundsForScrolling());
        EXPECT_EQ(expected,
            host_impl_->InnerViewportScrollLayer()->BoundsForScrolling());
    }

    // Test that scrolling the outer viewport affects the browser controls.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        BrowserControlsScrollOuterViewport)
    {
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            gfx::Size(100, 100), gfx::Size(200, 200), gfx::Size(200, 400));
        DrawFrame();

        EXPECT_EQ(top_controls_height_,
            host_impl_->browser_controls_manager()->ContentTopOffset());

        // Send a gesture scroll that will scroll the outer viewport, make sure the
        // browser controls get scrolled.
        gfx::Vector2dF scroll_delta(0.f, 15.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

        EXPECT_EQ(host_impl_->OuterViewportScrollLayer(),
            host_impl_->CurrentlyScrollingLayer());
        host_impl_->ScrollEnd(EndState().get());

        EXPECT_FLOAT_EQ(
            scroll_delta.y(),
            top_controls_height_ - host_impl_->browser_controls_manager()->ContentTopOffset());

        scroll_delta = gfx::Vector2dF(0.f, 50.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_EQ(host_impl_->OuterViewportScrollLayer(),
            host_impl_->CurrentlyScrollingLayer());

        host_impl_->ScrollEnd(EndState().get());

        // Position the viewports such that the inner viewport will be scrolled.
        gfx::Vector2dF inner_viewport_offset(0.f, 25.f);
        SetScrollOffsetDelta(host_impl_->OuterViewportScrollLayer(),
            gfx::Vector2dF());
        SetScrollOffsetDelta(host_impl_->InnerViewportScrollLayer(),
            inner_viewport_offset);

        scroll_delta = gfx::Vector2dF(0.f, -65.f);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

        EXPECT_EQ(top_controls_height_,
            host_impl_->browser_controls_manager()->ContentTopOffset());
        EXPECT_FLOAT_EQ(
            inner_viewport_offset.y() + (scroll_delta.y() + top_controls_height_),
            ScrollDelta(host_impl_->InnerViewportScrollLayer()).y());

        host_impl_->ScrollEnd(EndState().get());
    }

    TEST_F(LayerTreeHostImplBrowserControlsTest,
        ScrollNonScrollableRootWithBrowserControls)
    {
        settings_ = DefaultSettings();
        CreateHostImpl(settings_, CreateCompositorFrameSink());
        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            layer_size_, layer_size_, layer_size_);
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);

        host_impl_->browser_controls_manager()->ScrollBegin();
        host_impl_->browser_controls_manager()->ScrollBy(gfx::Vector2dF(0.f, 50.f));
        host_impl_->browser_controls_manager()->ScrollEnd();
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ContentTopOffset());
        // Now that browser controls have moved, expect the clip to resize.
        LayerImpl* inner_clip_ptr = host_impl_->InnerViewportScrollLayer()
                                        ->test_properties()
                                        ->parent->test_properties()
                                        ->parent;
        EXPECT_EQ(viewport_size_, inner_clip_ptr->bounds());

        host_impl_->ScrollEnd(EndState().get());

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);

        float scroll_increment_y = -25.f;
        host_impl_->browser_controls_manager()->ScrollBegin();
        host_impl_->browser_controls_manager()->ScrollBy(
            gfx::Vector2dF(0.f, scroll_increment_y));
        EXPECT_FLOAT_EQ(-scroll_increment_y,
            host_impl_->browser_controls_manager()->ContentTopOffset());
        // Now that browser controls have moved, expect the clip to resize.
        EXPECT_EQ(gfx::Size(viewport_size_.width(),
                      viewport_size_.height() + scroll_increment_y),
            inner_clip_ptr->bounds());

        host_impl_->browser_controls_manager()->ScrollBy(
            gfx::Vector2dF(0.f, scroll_increment_y));
        host_impl_->browser_controls_manager()->ScrollEnd();
        EXPECT_FLOAT_EQ(-2 * scroll_increment_y,
            host_impl_->browser_controls_manager()->ContentTopOffset());
        // Now that browser controls have moved, expect the clip to resize.
        EXPECT_EQ(clip_size_, inner_clip_ptr->bounds());

        host_impl_->ScrollEnd(EndState().get());

        // Verify the layer is once-again non-scrollable.
        EXPECT_EQ(
            gfx::ScrollOffset(),
            host_impl_->active_tree()->InnerViewportScrollLayer()->MaxScrollOffset());

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
    }

    // Tests that activating a pending tree while there's a bounds_delta on the
    // viewport layers from browser controls doesn't cause a scroll jump. This bug
    // was occurring because the UpdateViewportContainerSizes was being called
    // before the property trees were updated with the bounds_delta.
    // crbug.com/597266.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        ViewportBoundsDeltaOnTreeActivation)
    {
        const gfx::Size inner_viewport_size(1000, 1000);
        const gfx::Size outer_viewport_size(1000, 1000);
        const gfx::Size content_size(2000, 2000);

        // Initialization
        {
            SetupBrowserControlsAndScrollLayerWithVirtualViewport(
                inner_viewport_size, outer_viewport_size, content_size);
            host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);

            // Start off with the browser controls hidden on both main and impl.
            host_impl_->active_tree()->set_browser_controls_shrink_blink_size(false);
            host_impl_->active_tree()->PushBrowserControlsFromMainThread(0);

            host_impl_->CreatePendingTree();
            SetupBrowserControlsAndScrollLayerWithVirtualViewport(
                host_impl_->pending_tree(), inner_viewport_size, outer_viewport_size,
                content_size);
            host_impl_->pending_tree()->set_browser_controls_shrink_blink_size(false);

            // Fully scroll the viewport.
            host_impl_->ScrollBegin(BeginState(gfx::Point(75, 75)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2d(0, 2000)).get());
            host_impl_->ScrollEnd(EndState().get());
        }

        LayerImpl* outer_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();

        ASSERT_FLOAT_EQ(0,
            host_impl_->browser_controls_manager()->ContentTopOffset());
        ASSERT_EQ(1000, outer_scroll->MaxScrollOffset().y());
        ASSERT_EQ(1000, outer_scroll->CurrentScrollOffset().y());

        // Kick off an animation to show the browser controls.
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, true);
        base::TimeTicks start_time = base::TimeTicks::Now();
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        // Pump an animation frame to put some delta in the browser controls.
        {
            begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            host_impl_->UpdateAnimationState(true);
            host_impl_->DidFinishImplFrame();
        }

        // Pull the browser controls delta and get it back to the pending tree so that
        // when we go to activate the pending tree we cause a change to browser
        // controls.
        {
            float delta = host_impl_->active_tree()->top_controls_shown_ratio()->Delta();
            ASSERT_GT(delta, 0);
            ASSERT_LT(delta, 1);
            host_impl_->active_tree()
                ->top_controls_shown_ratio()
                ->PullDeltaForMainThread();
            host_impl_->active_tree()->top_controls_shown_ratio()->PushFromMainThread(
                delta);
        }

        // 200 is the kShowHideMaxDurationMs value from browser_controls_manager.cc so
        // the browser controls should be fully animated in this frame.
        {
            begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(200);
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            host_impl_->UpdateAnimationState(true);
            host_impl_->DidFinishImplFrame();

            ASSERT_EQ(50, host_impl_->browser_controls_manager()->ContentTopOffset());
            ASSERT_EQ(1050, outer_scroll->MaxScrollOffset().y());
            // NEAR because clip layer bounds are truncated in MaxScrollOffset so we
            // lose some precision in the intermediate animation steps.
            ASSERT_NEAR(1050, outer_scroll->CurrentScrollOffset().y(), 1.f);
        }

        // Activate the pending tree which should have the same scroll value as the
        // active tree.
        {
            host_impl_->pending_tree()
                ->property_trees()
                ->scroll_tree.SetScrollOffsetDeltaForTesting(outer_scroll->id(),
                    gfx::Vector2dF(0, 1050));
            host_impl_->ActivateSyncTree();

            // Make sure we don't accidentally clamp the outer offset based on a bounds
            // delta that hasn't yet been updated.
            EXPECT_NEAR(1050, outer_scroll->CurrentScrollOffset().y(), 1.f);
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollNonCompositedRoot)
    {
        // Test the configuration where a non-composited root layer is embedded in a
        // scrollable outer layer.
        gfx::Size surface_size(10, 10);
        gfx::Size contents_size(20, 20);

        std::unique_ptr<LayerImpl> content_layer = LayerImpl::Create(host_impl_->active_tree(), 11);
        content_layer->SetDrawsContent(true);
        content_layer->SetPosition(gfx::PointF());
        content_layer->SetBounds(contents_size);

        LayerImpl* scroll_clip_layer = CreateBasicVirtualViewportLayers(surface_size, surface_size);

        std::unique_ptr<LayerImpl> scroll_layer = LayerImpl::Create(host_impl_->active_tree(), 12);
        scroll_layer->SetScrollClipLayer(scroll_clip_layer->id());
        scroll_layer->SetBounds(contents_size);
        scroll_layer->SetPosition(gfx::PointF());
        scroll_layer->test_properties()->AddChild(std::move(content_layer));
        scroll_clip_layer->test_properties()->AddChild(std::move(scroll_layer));

        scroll_clip_layer->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_TRUE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, ScrollChildCallsCommitAndRedraw)
    {
        gfx::Size surface_size(10, 10);
        gfx::Size contents_size(20, 20);

        LayerImpl* root = CreateBasicVirtualViewportLayers(surface_size, surface_size);

        root->test_properties()->AddChild(
            CreateScrollableLayer(12, contents_size, root));
        root->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_TRUE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, ScrollMissesChild)
    {
        gfx::Size surface_size(10, 10);
        std::unique_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
        root->test_properties()->AddChild(
            CreateScrollableLayer(2, surface_size, root.get()));
        root->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        // Scroll event is ignored because the input coordinate is outside the layer
        // boundaries.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point(15, 5)).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);

        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, ScrollMissesBackfacingChild)
    {
        gfx::Size surface_size(10, 10);
        std::unique_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
        root->test_properties()->force_render_surface = true;
        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size, root.get());

        gfx::Transform matrix;
        matrix.RotateAboutXAxis(180.0);
        child->test_properties()->transform = matrix;
        child->test_properties()->double_sided = false;

        root->test_properties()->AddChild(std::move(child));
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        // Scroll event is ignored because the scrollable layer is not facing the
        // viewer and there is nothing scrollable behind it.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_IGNORED, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kNoScrollingLayer,
            status.main_thread_scrolling_reasons);

        EXPECT_FALSE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);
    }

    TEST_F(LayerTreeHostImplTest, ScrollBlockedByContentLayer)
    {
        gfx::Size surface_size(10, 10);
        std::unique_ptr<LayerImpl> clip_layer = LayerImpl::Create(host_impl_->active_tree(), 3);
        std::unique_ptr<LayerImpl> content_layer = CreateScrollableLayer(1, surface_size, clip_layer.get());
        content_layer->set_main_thread_scrolling_reasons(
            MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
        content_layer->SetScrollClipLayer(Layer::INVALID_ID);

        // Note: we can use the same clip layer for both since both calls to
        // CreateScrollableLayer() use the same surface size.
        std::unique_ptr<LayerImpl> scroll_layer = CreateScrollableLayer(2, surface_size, clip_layer.get());
        scroll_layer->test_properties()->AddChild(std::move(content_layer));
        clip_layer->test_properties()->AddChild(std::move(scroll_layer));
        clip_layer->test_properties()->force_render_surface = true;

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(clip_layer));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        // Scrolling fails because the content layer is asking to be scrolled on the
        // main thread.
        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnMainThread)
    {
        gfx::Size viewport_size(20, 20);
        float page_scale = 2.f;

        SetupScrollAndContentsLayers(viewport_size);

        // Setup the layers so that the outer viewport is scrollable.
        host_impl_->active_tree()
            ->InnerViewportScrollLayer()
            ->test_properties()
            ->parent->SetBounds(viewport_size);
        host_impl_->active_tree()->OuterViewportScrollLayer()->SetBounds(
            gfx::Size(40, 40));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
        DrawFrame();

        LayerImpl* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
        EXPECT_EQ(viewport_size, root_scroll->scroll_clip_layer()->bounds());

        gfx::Vector2d scroll_delta(0, 10);
        gfx::Vector2d expected_scroll_delta = scroll_delta;
        gfx::ScrollOffset expected_max_scroll = root_scroll->MaxScrollOffset();
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // Set new page scale from main thread.
        host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f, 2.f);

        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), inner_scroll->id(),
            expected_scroll_delta));

        // The scroll range should also have been updated.
        EXPECT_EQ(expected_max_scroll, root_scroll->MaxScrollOffset());

        // The page scale delta remains constant because the impl thread did not
        // scale.
        EXPECT_EQ(1.f, host_impl_->active_tree()->page_scale_delta());
    }

    TEST_F(LayerTreeHostImplTest, ScrollRootAndChangePageScaleOnImplThread)
    {
        gfx::Size viewport_size(20, 20);
        float page_scale = 2.f;

        SetupScrollAndContentsLayers(viewport_size);

        // Setup the layers so that the outer viewport is scrollable.
        host_impl_->active_tree()
            ->InnerViewportScrollLayer()
            ->test_properties()
            ->parent->SetBounds(viewport_size);
        host_impl_->active_tree()->OuterViewportScrollLayer()->SetBounds(
            gfx::Size(40, 40));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        LayerImpl* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->active_tree()->InnerViewportScrollLayer();
        EXPECT_EQ(viewport_size, root_scroll->scroll_clip_layer()->bounds());

        gfx::Vector2d scroll_delta(0, 10);
        gfx::Vector2d expected_scroll_delta = scroll_delta;
        gfx::ScrollOffset expected_max_scroll = root_scroll->MaxScrollOffset();
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // Set new page scale on impl thread by pinching.
        host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(page_scale, gfx::Point());
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        DrawOneFrame();

        // The scroll delta is not scaled because the main thread did not scale.
        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), inner_scroll->id(),
            expected_scroll_delta));

        // The scroll range should also have been updated.
        EXPECT_EQ(expected_max_scroll, root_scroll->MaxScrollOffset());

        // The page scale delta should match the new scale on the impl side.
        EXPECT_EQ(page_scale, host_impl_->active_tree()->current_page_scale_factor());
    }

    TEST_F(LayerTreeHostImplTest, PageScaleDeltaAppliedToRootScrollLayerOnly)
    {
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 2.f);
        gfx::Size surface_size(10, 10);
        float default_page_scale = 1.f;
        gfx::Transform default_page_scale_matrix;
        default_page_scale_matrix.Scale(default_page_scale, default_page_scale);

        float new_page_scale = 2.f;
        gfx::Transform new_page_scale_matrix;
        new_page_scale_matrix.Scale(new_page_scale, new_page_scale);

        // Create a normal scrollable root layer and another scrollable child layer.
        LayerImpl* scroll = SetupScrollAndContentsLayers(surface_size);
        scroll->SetDrawsContent(true);
        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
        LayerImpl* child = scroll->test_properties()->children[0];
        child->SetDrawsContent(true);

        std::unique_ptr<LayerImpl> scrollable_child_clip = LayerImpl::Create(host_impl_->active_tree(), 6);
        std::unique_ptr<LayerImpl> scrollable_child = CreateScrollableLayer(7, surface_size, scrollable_child_clip.get());
        scrollable_child_clip->test_properties()->AddChild(
            std::move(scrollable_child));
        child->test_properties()->AddChild(std::move(scrollable_child_clip));
        LayerImpl* grand_child = child->test_properties()->children[0];
        grand_child->SetDrawsContent(true);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        // Set new page scale on impl thread by pinching.
        host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(new_page_scale, gfx::Point());
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());
        DrawOneFrame();

        // Make sure all the layers are drawn with the page scale delta applied, i.e.,
        // the page scale delta on the root layer is applied hierarchically.
        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        EXPECT_EQ(1.f, root->DrawTransform().matrix().getDouble(0, 0));
        EXPECT_EQ(1.f, root->DrawTransform().matrix().getDouble(1, 1));
        EXPECT_EQ(new_page_scale, scroll->DrawTransform().matrix().getDouble(0, 0));
        EXPECT_EQ(new_page_scale, scroll->DrawTransform().matrix().getDouble(1, 1));
        EXPECT_EQ(new_page_scale, child->DrawTransform().matrix().getDouble(0, 0));
        EXPECT_EQ(new_page_scale, child->DrawTransform().matrix().getDouble(1, 1));
        EXPECT_EQ(new_page_scale,
            grand_child->DrawTransform().matrix().getDouble(0, 0));
        EXPECT_EQ(new_page_scale,
            grand_child->DrawTransform().matrix().getDouble(1, 1));
    }

    TEST_F(LayerTreeHostImplTest, ScrollChildAndChangePageScaleOnMainThread)
    {
        SetupScrollAndContentsLayers(gfx::Size(30, 30));

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();

        // Make the outer scroll layer scrollable.
        outer_scroll->SetBounds(gfx::Size(50, 50));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        gfx::Vector2d scroll_delta(0, 10);
        gfx::Vector2d expected_scroll_delta(scroll_delta);
        gfx::ScrollOffset expected_max_scroll(outer_scroll->MaxScrollOffset());
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        float page_scale = 2.f;
        host_impl_->active_tree()->PushPageScaleFromMainThread(page_scale, 1.f,
            page_scale);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawOneFrame();

        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), inner_scroll->id(),
            expected_scroll_delta));

        // The scroll range should not have changed.
        EXPECT_EQ(outer_scroll->MaxScrollOffset(), expected_max_scroll);

        // The page scale delta remains constant because the impl thread did not
        // scale.
        EXPECT_EQ(1.f, host_impl_->active_tree()->page_scale_delta());
    }

    TEST_F(LayerTreeHostImplTest, ScrollChildBeyondLimit)
    {
        // Scroll a child layer beyond its maximum scroll range and make sure the
        // parent layer isn't scrolled.
        gfx::Size surface_size(10, 10);
        gfx::Size content_size(20, 20);

        LayerImpl* root = CreateBasicVirtualViewportLayers(surface_size, surface_size);

        root->test_properties()->force_render_surface = true;
        std::unique_ptr<LayerImpl> grand_child = CreateScrollableLayer(13, content_size, root);

        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(12, content_size, root);
        LayerImpl* grand_child_layer = grand_child.get();
        child->test_properties()->AddChild(std::move(grand_child));

        LayerImpl* child_layer = child.get();
        root->test_properties()->AddChild(std::move(child));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        grand_child_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(grand_child_layer->id(),
                gfx::ScrollOffset(0, 5));
        child_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(child_layer->id(),
                gfx::ScrollOffset(3, 0));

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();
        {
            gfx::Vector2d scroll_delta(-8, -7);
            EXPECT_EQ(
                InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                    .thread);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();

            // The grand child should have scrolled up to its limit.
            LayerImpl* child = host_impl_->active_tree()
                                   ->root_layer_for_testing()
                                   ->test_properties()
                                   ->children[0];
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), grand_child_layer->id(),
                gfx::Vector2d(0, -5)));

            // The child should not have scrolled.
            ExpectNone(*scroll_info.get(), child->id());
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollWithoutBubbling)
    {
        // Scroll a child layer beyond its maximum scroll range and make sure the
        // the scroll doesn't bubble up to the parent layer.
        gfx::Size surface_size(20, 20);
        gfx::Size viewport_size(10, 10);
        std::unique_ptr<LayerImpl> root_ptr = LayerImpl::Create(host_impl_->active_tree(), 1);
        std::unique_ptr<LayerImpl> root_clip = LayerImpl::Create(host_impl_->active_tree(), 2);
        root_clip->test_properties()->force_render_surface = true;
        std::unique_ptr<LayerImpl> root_scrolling = CreateScrollableLayer(3, surface_size, root_clip.get());
        root_scrolling->test_properties()->is_container_for_fixed_position_layers = true;

        std::unique_ptr<LayerImpl> grand_child = CreateScrollableLayer(5, surface_size, root_clip.get());

        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(4, surface_size, root_clip.get());
        LayerImpl* grand_child_layer = grand_child.get();
        child->test_properties()->AddChild(std::move(grand_child));

        LayerImpl* child_layer = child.get();
        root_scrolling->test_properties()->AddChild(std::move(child));
        root_clip->test_properties()->AddChild(std::move(root_scrolling));
        EXPECT_EQ(viewport_size, root_clip->bounds());
        root_ptr->test_properties()->AddChild(std::move(root_clip));
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 3,
            Layer::INVALID_ID);
        host_impl_->active_tree()->DidBecomeActive();
        host_impl_->SetViewportSize(viewport_size);

        grand_child_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(grand_child_layer->id(),
                gfx::ScrollOffset(0, 2));
        child_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(child_layer->id(),
                gfx::ScrollOffset(0, 3));

        DrawFrame();
        {
            gfx::Vector2d scroll_delta(0, -10);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();

            // The grand child should have scrolled up to its limit.
            LayerImpl* child = host_impl_->active_tree()
                                   ->root_layer_for_testing()
                                   ->test_properties()
                                   ->children[0]
                                   ->test_properties()
                                   ->children[0]
                                   ->test_properties()
                                   ->children[0];
            LayerImpl* grand_child = child->test_properties()->children[0];
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), grand_child->id(),
                gfx::Vector2d(0, -2)));

            // The child should not have scrolled.
            ExpectNone(*scroll_info.get(), child->id());

            // The next time we scroll we should only scroll the parent.
            scroll_delta = gfx::Vector2d(0, -3);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child);
            host_impl_->ScrollEnd(EndState().get());

            scroll_info = host_impl_->ProcessScrollDeltas();

            // The child should have scrolled up to its limit.
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child->id(),
                gfx::Vector2d(0, -3)));

            // The grand child should not have scrolled.
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), grand_child->id(),
                gfx::Vector2d(0, -2)));

            // After scrolling the parent, another scroll on the opposite direction
            // should still scroll the child.
            scroll_delta = gfx::Vector2d(0, 7);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
            host_impl_->ScrollEnd(EndState().get());

            scroll_info = host_impl_->ProcessScrollDeltas();

            // The grand child should have scrolled.
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), grand_child->id(),
                gfx::Vector2d(0, 5)));

            // The child should not have scrolled.
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child->id(),
                gfx::Vector2d(0, -3)));

            // Scrolling should be adjusted from viewport space.
            host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 2.f, 2.f);
            host_impl_->active_tree()->SetPageScaleOnActiveTree(2.f);

            scroll_delta = gfx::Vector2d(0, -2);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(1, 1)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            EXPECT_EQ(grand_child, host_impl_->CurrentlyScrollingLayer());
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            scroll_info = host_impl_->ProcessScrollDeltas();

            // Should have scrolled by half the amount in layer space (5 - 2/2)
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), grand_child->id(),
                gfx::Vector2d(0, 4)));
        }
    }
    TEST_F(LayerTreeHostImplTest, ScrollEventBubbling)
    {
        // When we try to scroll a non-scrollable child layer, the scroll delta
        // should be applied to one of its ancestors if possible.
        gfx::Size surface_size(10, 10);
        gfx::Size content_size(20, 20);
        std::unique_ptr<LayerImpl> root_ptr = LayerImpl::Create(host_impl_->active_tree(), 4);
        std::unique_ptr<LayerImpl> root_clip = LayerImpl::Create(host_impl_->active_tree(), 3);
        root_clip->test_properties()->force_render_surface = true;
        std::unique_ptr<LayerImpl> root_scroll = CreateScrollableLayer(1, content_size, root_clip.get());
        // Make 'root' the clip layer for child: since they have the same sizes the
        // child will have zero max_scroll_offset and scrolls will bubble.
        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(2, content_size, root_scroll.get());
        child->test_properties()->is_container_for_fixed_position_layers = true;
        root_scroll->SetBounds(content_size);

        int root_scroll_id = root_scroll->id();
        root_scroll->test_properties()->AddChild(std::move(child));
        root_clip->test_properties()->AddChild(std::move(root_scroll));
        root_ptr->test_properties()->AddChild(std::move(root_clip));

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr));
        host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 4, 2,
            Layer::INVALID_ID);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();
        {
            gfx::Vector2d scroll_delta(0, 4);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::WHEEL)
                    .thread);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();

            // Only the root scroll should have scrolled.
            ASSERT_EQ(scroll_info->scrolls.size(), 1u);
            EXPECT_TRUE(
                ScrollInfoContains(*scroll_info.get(), root_scroll_id, scroll_delta));
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollBeforeRedraw)
    {
        gfx::Size surface_size(10, 10);
        std::unique_ptr<LayerImpl> root_ptr = LayerImpl::Create(host_impl_->active_tree(), 1);
        std::unique_ptr<LayerImpl> inner_clip = LayerImpl::Create(host_impl_->active_tree(), 2);
        std::unique_ptr<LayerImpl> inner_scroll = CreateScrollableLayer(3, surface_size, inner_clip.get());
        std::unique_ptr<LayerImpl> outer_clip = LayerImpl::Create(host_impl_->active_tree(), 7);
        std::unique_ptr<LayerImpl> outer_scroll = CreateScrollableLayer(8, surface_size, outer_clip.get());
        inner_clip->test_properties()->force_render_surface = true;
        inner_scroll->test_properties()->is_container_for_fixed_position_layers = true;
        outer_scroll->test_properties()->is_container_for_fixed_position_layers = true;
        outer_clip->test_properties()->AddChild(std::move(outer_scroll));
        inner_scroll->test_properties()->AddChild(std::move(outer_clip));
        inner_clip->test_properties()->AddChild(std::move(inner_scroll));
        root_ptr->test_properties()->AddChild(std::move(inner_clip));
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr));
        host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 1, 3,
            8);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        host_impl_->SetViewportSize(surface_size);

        // Draw one frame and then immediately rebuild the layer tree to mimic a tree
        // synchronization.
        DrawFrame();

        host_impl_->active_tree()->DetachLayers();
        std::unique_ptr<LayerImpl> root_ptr2 = LayerImpl::Create(host_impl_->active_tree(), 4);
        std::unique_ptr<LayerImpl> inner_clip2 = LayerImpl::Create(host_impl_->active_tree(), 5);
        std::unique_ptr<LayerImpl> inner_scroll2 = CreateScrollableLayer(6, surface_size, inner_clip2.get());
        std::unique_ptr<LayerImpl> outer_clip2 = LayerImpl::Create(host_impl_->active_tree(), 9);
        std::unique_ptr<LayerImpl> outer_scroll2 = CreateScrollableLayer(10, surface_size, outer_clip2.get());
        inner_scroll2->test_properties()->is_container_for_fixed_position_layers = true;
        outer_scroll2->test_properties()->is_container_for_fixed_position_layers = true;
        outer_clip2->test_properties()->AddChild(std::move(outer_scroll2));
        inner_scroll2->test_properties()->AddChild(std::move(outer_clip2));
        inner_clip2->test_properties()->AddChild(std::move(inner_scroll2));
        inner_clip2->test_properties()->force_render_surface = true;
        root_ptr2->test_properties()->AddChild(std::move(inner_clip2));
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr2));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 4, 6,
            10);
        host_impl_->active_tree()->DidBecomeActive();

        // Scrolling should still work even though we did not draw yet.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(), InputHandler::WHEEL)
                .thread);
    }

    TEST_F(LayerTreeHostImplTest, ScrollAxisAlignedRotatedLayer)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        scroll_layer->SetDrawsContent(true);

        // Rotate the root layer 90 degrees counter-clockwise about its center.
        gfx::Transform rotate_transform;
        rotate_transform.Rotate(-90.0);
        host_impl_->active_tree()
            ->root_layer_for_testing()
            ->test_properties()
            ->transform
            = rotate_transform;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        gfx::Size surface_size(50, 50);
        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        // Scroll to the right in screen coordinates with a gesture.
        gfx::Vector2d gesture_scroll_delta(10, 0);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), gesture_scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // The layer should have scrolled down in its local coordinates.
        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), scroll_layer->id(),
            gfx::Vector2d(0, gesture_scroll_delta.x())));

        // Reset and scroll down with the wheel.
        SetScrollOffsetDelta(scroll_layer, gfx::Vector2dF());
        gfx::Vector2d wheel_scroll_delta(0, 10);
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), wheel_scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // The layer should have scrolled down in its local coordinates.
        scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), scroll_layer->id(),
            wheel_scroll_delta));
    }

    TEST_F(LayerTreeHostImplTest, ScrollNonAxisAlignedRotatedLayer)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        int child_clip_layer_id = 6;
        int child_layer_id = 7;
        float child_layer_angle = -20.f;

        // Create a child layer that is rotated to a non-axis-aligned angle.
        std::unique_ptr<LayerImpl> clip_layer = LayerImpl::Create(host_impl_->active_tree(), child_clip_layer_id);
        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(
            child_layer_id, scroll_layer->bounds(), clip_layer.get());
        gfx::Transform rotate_transform;
        rotate_transform.Translate(-50.0, -50.0);
        rotate_transform.Rotate(child_layer_angle);
        rotate_transform.Translate(50.0, 50.0);
        clip_layer->test_properties()->transform = rotate_transform;

        // Only allow vertical scrolling.
        clip_layer->SetBounds(
            gfx::Size(child->bounds().width(), child->bounds().height() / 2));
        // The rotation depends on the layer's transform origin, and the child layer
        // is a different size than the clip, so make sure the clip layer's origin
        // lines up over the child.
        clip_layer->test_properties()->transform_origin = gfx::Point3F(
            clip_layer->bounds().width() * 0.5f, clip_layer->bounds().height(), 0.f);
        LayerImpl* child_ptr = child.get();
        clip_layer->test_properties()->AddChild(std::move(child));
        scroll_layer->test_properties()->AddChild(std::move(clip_layer));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        gfx::Size surface_size(50, 50);
        host_impl_->SetViewportSize(surface_size);
        DrawFrame();
        {
            // Scroll down in screen coordinates with a gesture.
            gfx::Vector2d gesture_scroll_delta(0, 10);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(1, 1)).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), gesture_scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            // The child layer should have scrolled down in its local coordinates an
            // amount proportional to the angle between it and the input scroll delta.
            gfx::Vector2d expected_scroll_delta(
                0, gesture_scroll_delta.y() * std::cos(MathUtil::Deg2Rad(child_layer_angle)));
            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child_layer_id,
                expected_scroll_delta));

            // The root scroll layer should not have scrolled, because the input delta
            // was close to the layer's axis of movement.
            EXPECT_EQ(scroll_info->scrolls.size(), 1u);
        }
        {
            // Now reset and scroll the same amount horizontally.
            SetScrollOffsetDelta(child_ptr, gfx::Vector2dF());
            gfx::Vector2d gesture_scroll_delta(10, 0);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(1, 1)).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), gesture_scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());

            // The child layer shouldn't have scrolled.
            gfx::Vector2d expected_scroll_delta(
                0, -gesture_scroll_delta.x() * std::sin(MathUtil::Deg2Rad(child_layer_angle)));
            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child_layer_id,
                expected_scroll_delta));

            // The root scroll layer shouldn't have scrolled.
            ExpectNone(*scroll_info.get(), scroll_layer->id());
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollPerspectiveTransformedLayer)
    {
        // When scrolling an element with perspective, the distance scrolled
        // depends on the point at which the scroll begins.
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        int child_clip_layer_id = 6;
        int child_layer_id = 7;

        // Create a child layer that is rotated on its x axis, with perspective.
        std::unique_ptr<LayerImpl> clip_layer = LayerImpl::Create(host_impl_->active_tree(), child_clip_layer_id);
        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(
            child_layer_id, scroll_layer->bounds(), clip_layer.get());
        LayerImpl* child_ptr = child.get();
        gfx::Transform perspective_transform;
        perspective_transform.Translate(-50.0, -50.0);
        perspective_transform.ApplyPerspectiveDepth(20);
        perspective_transform.RotateAboutXAxis(45);
        perspective_transform.Translate(50.0, 50.0);
        clip_layer->test_properties()->transform = perspective_transform;

        clip_layer->SetBounds(gfx::Size(child_ptr->bounds().width() / 2,
            child_ptr->bounds().height() / 2));
        // The transform depends on the layer's transform origin, and the child layer
        // is a different size than the clip, so make sure the clip layer's origin
        // lines up over the child.
        clip_layer->test_properties()->transform_origin = gfx::Point3F(
            clip_layer->bounds().width(), clip_layer->bounds().height(), 0.f);
        clip_layer->test_properties()->AddChild(std::move(child));
        scroll_layer->test_properties()->AddChild(std::move(clip_layer));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        gfx::Size surface_size(50, 50);
        host_impl_->SetViewportSize(surface_size);

        std::unique_ptr<ScrollAndScaleSet> scroll_info;

        gfx::Vector2d gesture_scroll_deltas[4];
        gesture_scroll_deltas[0] = gfx::Vector2d(4, 10);
        gesture_scroll_deltas[1] = gfx::Vector2d(4, 10);
        gesture_scroll_deltas[2] = gfx::Vector2d(10, 0);
        gesture_scroll_deltas[3] = gfx::Vector2d(10, 0);

        gfx::Vector2d expected_scroll_deltas[4];
        // Perspective affects the vertical delta by a different
        // amount depending on the vertical position of the |viewport_point|.
        expected_scroll_deltas[0] = gfx::Vector2d(2, 8);
        expected_scroll_deltas[1] = gfx::Vector2d(1, 4);
        // Deltas which start with the same vertical position of the
        // |viewport_point| are subject to identical perspective effects.
        expected_scroll_deltas[2] = gfx::Vector2d(4, 0);
        expected_scroll_deltas[3] = gfx::Vector2d(4, 0);

        gfx::Point viewport_point(1, 1);

        // Scroll in screen coordinates with a gesture. Each scroll starts
        // where the previous scroll ended, but the scroll position is reset
        // for each scroll.
        for (int i = 0; i < 4; ++i) {
            SetScrollOffsetDelta(child_ptr, gfx::Vector2dF());
            DrawFrame();
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(viewport_point).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            host_impl_->ScrollBy(
                UpdateState(viewport_point, gesture_scroll_deltas[i]).get());
            viewport_point += gesture_scroll_deltas[i];
            host_impl_->ScrollEnd(EndState().get());

            scroll_info = host_impl_->ProcessScrollDeltas();
            EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), child_layer_id,
                expected_scroll_deltas[i]));

            // The root scroll layer should not have scrolled, because the input delta
            // was close to the layer's axis of movement.
            EXPECT_EQ(scroll_info->scrolls.size(), 1u);
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollScaledLayer)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));

        // Scale the layer to twice its normal size.
        int scale = 2;
        gfx::Transform scale_transform;
        scale_transform.Scale(scale, scale);
        scroll_layer->test_properties()->parent->test_properties()->transform = scale_transform;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        gfx::Size surface_size(50, 50);
        host_impl_->SetViewportSize(surface_size);
        DrawFrame();

        // Scroll down in screen coordinates with a gesture.
        gfx::Vector2d scroll_delta(0, 10);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // The layer should have scrolled down in its local coordinates, but half the
        // amount.
        std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), scroll_layer->id(),
            gfx::Vector2d(0, scroll_delta.y() / scale)));

        // Reset and scroll down with the wheel.
        SetScrollOffsetDelta(scroll_layer, gfx::Vector2dF());
        gfx::Vector2d wheel_scroll_delta(0, 10);
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), wheel_scroll_delta).get());
        host_impl_->ScrollEnd(EndState().get());

        // It should apply the scale factor to the scroll delta for the wheel event.
        scroll_info = host_impl_->ProcessScrollDeltas();
        EXPECT_TRUE(ScrollInfoContains(*scroll_info.get(), scroll_layer->id(),
            wheel_scroll_delta));
    }

    TEST_F(LayerTreeHostImplTest, ScrollViewportRounding)
    {
        int width = 332;
        int height = 20;
        int scale = 3;
        SetupScrollAndContentsLayers(gfx::Size(width, height));
        host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
            gfx::Size(width * scale - 1, height * scale));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->active_tree()->SetDeviceScaleFactor(scale);
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);

        LayerImpl* inner_viewport_scroll_layer = host_impl_->active_tree()->InnerViewportScrollLayer();
        EXPECT_EQ(gfx::ScrollOffset(0, 0),
            inner_viewport_scroll_layer->MaxScrollOffset());
    }

    class TestInputHandlerClient : public InputHandlerClient {
    public:
        TestInputHandlerClient()
            : page_scale_factor_(0.f)
            , min_page_scale_factor_(-1.f)
            , max_page_scale_factor_(-1.f)
        {
        }
        ~TestInputHandlerClient() override { }

        // InputHandlerClient implementation.
        void WillShutdown() override { }
        void Animate(base::TimeTicks time) override { }
        void MainThreadHasStoppedFlinging() override { }
        void ReconcileElasticOverscrollAndRootScroll() override { }
        void UpdateRootLayerStateForSynchronousInputHandler(
            const gfx::ScrollOffset& total_scroll_offset,
            const gfx::ScrollOffset& max_scroll_offset,
            const gfx::SizeF& scrollable_size,
            float page_scale_factor,
            float min_page_scale_factor,
            float max_page_scale_factor) override
        {
            DCHECK(total_scroll_offset.x() <= max_scroll_offset.x());
            DCHECK(total_scroll_offset.y() <= max_scroll_offset.y());
            last_set_scroll_offset_ = total_scroll_offset;
            max_scroll_offset_ = max_scroll_offset;
            scrollable_size_ = scrollable_size;
            page_scale_factor_ = page_scale_factor;
            min_page_scale_factor_ = min_page_scale_factor;
            max_page_scale_factor_ = max_page_scale_factor;
        }
        void DeliverInputForBeginFrame() override { }

        gfx::ScrollOffset last_set_scroll_offset() { return last_set_scroll_offset_; }

        gfx::ScrollOffset max_scroll_offset() const { return max_scroll_offset_; }

        gfx::SizeF scrollable_size() const { return scrollable_size_; }

        float page_scale_factor() const { return page_scale_factor_; }

        float min_page_scale_factor() const { return min_page_scale_factor_; }

        float max_page_scale_factor() const { return max_page_scale_factor_; }

    private:
        gfx::ScrollOffset last_set_scroll_offset_;
        gfx::ScrollOffset max_scroll_offset_;
        gfx::SizeF scrollable_size_;
        float page_scale_factor_;
        float min_page_scale_factor_;
        float max_page_scale_factor_;
    };

    TEST_F(LayerTreeHostImplTest, RootLayerScrollOffsetDelegation)
    {
        TestInputHandlerClient scroll_watcher;
        host_impl_->SetViewportSize(gfx::Size(10, 20));
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        LayerImpl* clip_layer = scroll_layer->test_properties()->parent->test_properties()->parent;
        clip_layer->SetBounds(gfx::Size(10, 20));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->BindToClient(&scroll_watcher);

        gfx::Vector2dF initial_scroll_delta(10.f, 10.f);
        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->id(),
                gfx::ScrollOffset());
        SetScrollOffsetDelta(scroll_layer, initial_scroll_delta);

        EXPECT_EQ(gfx::ScrollOffset(), scroll_watcher.last_set_scroll_offset());

        // Requesting an update results in the current scroll offset being set.
        host_impl_->RequestUpdateForSynchronousInputHandler();
        EXPECT_EQ(gfx::ScrollOffset(initial_scroll_delta),
            scroll_watcher.last_set_scroll_offset());

        // Setting the delegate results in the scrollable_size, max_scroll_offset,
        // page_scale_factor and {min|max}_page_scale_factor being set.
        EXPECT_EQ(gfx::SizeF(100, 100), scroll_watcher.scrollable_size());
        EXPECT_EQ(gfx::ScrollOffset(90, 80), scroll_watcher.max_scroll_offset());
        EXPECT_EQ(1.f, scroll_watcher.page_scale_factor());
        EXPECT_EQ(1.f, scroll_watcher.min_page_scale_factor());
        EXPECT_EQ(1.f, scroll_watcher.max_page_scale_factor());

        // Put a page scale on the tree.
        host_impl_->active_tree()->PushPageScaleFromMainThread(2.f, 0.5f, 4.f);
        EXPECT_EQ(1.f, scroll_watcher.page_scale_factor());
        EXPECT_EQ(1.f, scroll_watcher.min_page_scale_factor());
        EXPECT_EQ(1.f, scroll_watcher.max_page_scale_factor());
        // Activation will update the delegate.
        host_impl_->ActivateSyncTree();
        EXPECT_EQ(2.f, scroll_watcher.page_scale_factor());
        EXPECT_EQ(.5f, scroll_watcher.min_page_scale_factor());
        EXPECT_EQ(4.f, scroll_watcher.max_page_scale_factor());

        // Animating page scale can change the root offset, so it should update the
        // delegate. Also resets the page scale to 1 for the rest of the test.
        host_impl_->LayerTreeHostImpl::StartPageScaleAnimation(
            gfx::Vector2d(0, 0), false, 1.f, base::TimeDelta());
        host_impl_->Animate();
        EXPECT_EQ(1.f, scroll_watcher.page_scale_factor());
        EXPECT_EQ(.5f, scroll_watcher.min_page_scale_factor());
        EXPECT_EQ(4.f, scroll_watcher.max_page_scale_factor());

        // The pinch gesture doesn't put the delegate into a state where the scroll
        // offset is outside of the scroll range.  (this is verified by DCHECKs in the
        // delegate).
        host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
            InputHandler::TOUCHSCREEN);
        host_impl_->PinchGestureBegin();
        host_impl_->PinchGestureUpdate(2.f, gfx::Point());
        host_impl_->PinchGestureUpdate(.5f, gfx::Point());
        host_impl_->PinchGestureEnd();
        host_impl_->ScrollEnd(EndState().get());

        // Scrolling should be relative to the offset as given by the delegate.
        gfx::Vector2dF scroll_delta(0.f, 10.f);
        gfx::ScrollOffset current_offset(7.f, 8.f);

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        host_impl_->SetSynchronousInputHandlerRootScrollOffset(current_offset);

        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        EXPECT_EQ(ScrollOffsetWithDelta(current_offset, scroll_delta),
            scroll_watcher.last_set_scroll_offset());

        current_offset = gfx::ScrollOffset(42.f, 41.f);
        host_impl_->SetSynchronousInputHandlerRootScrollOffset(current_offset);
        host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
        EXPECT_EQ(current_offset + gfx::ScrollOffset(scroll_delta),
            scroll_watcher.last_set_scroll_offset());
        host_impl_->ScrollEnd(EndState().get());
        host_impl_->SetSynchronousInputHandlerRootScrollOffset(gfx::ScrollOffset());

        // Forces a full tree synchronization and ensures that the scroll delegate
        // sees the correct size of the new tree.
        gfx::Size new_size(42, 24);
        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
        CreateScrollAndContentsLayers(host_impl_->pending_tree(), new_size);
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();
        host_impl_->ActivateSyncTree();
        EXPECT_EQ(gfx::SizeF(new_size), scroll_watcher.scrollable_size());

        // Tear down the LayerTreeHostImpl before the InputHandlerClient.
        host_impl_->ReleaseCompositorFrameSink();
        host_impl_ = nullptr;
    }

    void CheckLayerScrollDelta(LayerImpl* layer, gfx::Vector2dF scroll_delta)
    {
        const gfx::Transform target_space_transform = layer->draw_properties().target_space_transform;
        EXPECT_TRUE(target_space_transform.IsScaleOrTranslation());
        gfx::Point translated_point;
        target_space_transform.TransformPoint(&translated_point);
        gfx::Point expected_point = gfx::Point() - ToRoundedVector2d(scroll_delta);
        EXPECT_EQ(expected_point.ToString(), translated_point.ToString());
    }

    TEST_F(LayerTreeHostImplTest,
        ExternalRootLayerScrollOffsetDelegationReflectedInNextDraw)
    {
        host_impl_->SetViewportSize(gfx::Size(10, 20));
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        LayerImpl* clip_layer = scroll_layer->test_properties()->parent->test_properties()->parent;
        clip_layer->SetBounds(gfx::Size(10, 20));
        scroll_layer->SetDrawsContent(true);

        // Draw first frame to clear any pending draws and check scroll.
        DrawFrame();
        CheckLayerScrollDelta(scroll_layer, gfx::Vector2dF(0.f, 0.f));
        EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());

        // Set external scroll delta on delegate and notify LayerTreeHost.
        gfx::ScrollOffset scroll_offset(10.f, 10.f);
        host_impl_->SetSynchronousInputHandlerRootScrollOffset(scroll_offset);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        // Check scroll delta reflected in layer.
        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);
        EXPECT_FALSE(frame.has_no_damage);
        CheckLayerScrollDelta(scroll_layer, ScrollOffsetToVector2dF(scroll_offset));
    }

    TEST_F(LayerTreeHostImplTest, OverscrollRoot)
    {
        InputHandlerScrollResult scroll_result;
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
        DrawFrame();
        EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());

        // In-bounds scrolling does not affect overscroll.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_FALSE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());

        // Overscroll events are reflected immediately.
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, 50)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, 10), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        // In-bounds scrolling resets accumulated overscroll for the scrolled axes.
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, -50)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_FALSE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, 0), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, -10)).get());
        EXPECT_FALSE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, -10), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(10, 0)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_FALSE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, 0), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(-15, 0)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(-5, 0), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(-5, -10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, 60)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, 10), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(-5, 10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(10, -60)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, -10), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        // Overscroll accumulates within the scope of ScrollBegin/ScrollEnd as long
        // as no scroll occurs.
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, -20)).get());
        EXPECT_FALSE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, -20), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, -30), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, -20)).get());
        EXPECT_FALSE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, -20), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, -50), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        // Overscroll resets on valid scroll.
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_FALSE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, 0), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, 0), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, -20)).get());
        EXPECT_TRUE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, -10), scroll_result.unused_scroll_delta);
        EXPECT_EQ(gfx::Vector2dF(0, -10), host_impl_->accumulated_root_overscroll());
        EXPECT_EQ(scroll_result.accumulated_root_overscroll,
            host_impl_->accumulated_root_overscroll());

        host_impl_->ScrollEnd(EndState().get());
    }

    TEST_F(LayerTreeHostImplTest, OverscrollChildWithoutBubbling)
    {
        // Scroll child layers beyond their maximum scroll range and make sure root
        // overscroll does not accumulate.
        InputHandlerScrollResult scroll_result;
        gfx::Size surface_size(10, 10);
        std::unique_ptr<LayerImpl> root_clip = LayerImpl::Create(host_impl_->active_tree(), 4);
        root_clip->test_properties()->force_render_surface = true;

        std::unique_ptr<LayerImpl> root = CreateScrollableLayer(1, surface_size, root_clip.get());

        std::unique_ptr<LayerImpl> grand_child = CreateScrollableLayer(3, surface_size, root_clip.get());

        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(2, surface_size, root_clip.get());
        LayerImpl* grand_child_layer = grand_child.get();
        child->test_properties()->AddChild(std::move(grand_child));

        host_impl_->active_tree()->SetViewportLayersFromIds(
            Layer::INVALID_ID, Layer::INVALID_ID, root->id(), Layer::INVALID_ID);

        LayerImpl* child_layer = child.get();
        root->test_properties()->AddChild(std::move(child));
        root_clip->test_properties()->AddChild(std::move(root));
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_clip));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        child_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(child_layer->id(),
                gfx::ScrollOffset(0, 3));
        grand_child_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(grand_child_layer->id(),
                gfx::ScrollOffset(0, 2));

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();
        {
            gfx::Vector2d scroll_delta(0, -10);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            scroll_result = host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
            host_impl_->ScrollEnd(EndState().get());

            // The next time we scroll we should only scroll the parent, but overscroll
            // should still not reach the root layer.
            scroll_delta = gfx::Vector2d(0, -30);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child_layer);
            EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            scroll_result = host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer()->id(), child_layer->id());
            EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
            host_impl_->ScrollEnd(EndState().get());

            // After scrolling the parent, another scroll on the opposite direction
            // should scroll the child.
            scroll_delta = gfx::Vector2d(0, 70);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child_layer);
            scroll_result = host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child_layer);
            EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
            host_impl_->ScrollEnd(EndState().get());
        }
    }

    TEST_F(LayerTreeHostImplTest, OverscrollChildEventBubbling)
    {
        // When we try to scroll a non-scrollable child layer, the scroll delta
        // should be applied to one of its ancestors if possible. Overscroll should
        // be reflected only when it has bubbled up to the root scrolling layer.
        InputHandlerScrollResult scroll_result;
        SetupScrollAndContentsLayers(gfx::Size(20, 20));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();
        {
            gfx::Vector2d scroll_delta(0, 8);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                        InputHandler::WHEEL)
                    .thread);
            scroll_result = host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
            scroll_result = host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_TRUE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF(0, 6), host_impl_->accumulated_root_overscroll());
            scroll_result = host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_FALSE(scroll_result.did_scroll);
            EXPECT_TRUE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF(0, 14), host_impl_->accumulated_root_overscroll());
            host_impl_->ScrollEnd(EndState().get());
        }
    }

    TEST_F(LayerTreeHostImplTest, OverscrollAlways)
    {
        InputHandlerScrollResult scroll_result;
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());

        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(50, 50));
        LayerImpl* clip_layer = scroll_layer->test_properties()->parent->test_properties()->parent;

        clip_layer->SetBounds(gfx::Size(50, 50));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 0.5f, 4.f);
        DrawFrame();
        EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());

        // Even though the layer can't scroll the overscroll still happens.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get());
        EXPECT_FALSE(scroll_result.did_scroll);
        EXPECT_TRUE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(0, 10), host_impl_->accumulated_root_overscroll());
    }

    TEST_F(LayerTreeHostImplTest, NoOverscrollWhenNotAtEdge)
    {
        InputHandlerScrollResult scroll_result;
        gfx::Size viewport_size(100, 100);
        gfx::Size content_size(200, 200);
        LayerImpl* root_scroll_layer = CreateBasicVirtualViewportLayers(viewport_size, viewport_size);
        host_impl_->active_tree()->OuterViewportScrollLayer()->SetBounds(
            content_size);
        root_scroll_layer->SetBounds(content_size);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();
        {
            // Edge glow effect should be applicable only upon reaching Edges
            // of the content. unnecessary glow effect calls shouldn't be
            // called while scrolling up without reaching the edge of the content.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                        InputHandler::WHEEL)
                    .thread);
            scroll_result = host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2dF(0, 100)).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF().ToString(),
                host_impl_->accumulated_root_overscroll().ToString());
            scroll_result = host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2dF(0, -2.30f)).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF().ToString(),
                host_impl_->accumulated_root_overscroll().ToString());
            host_impl_->ScrollEnd(EndState().get());
            // unusedrootDelta should be subtracted from applied delta so that
            // unwanted glow effect calls are not called.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                        InputHandler::NON_BUBBLING_GESTURE)
                    .thread);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            scroll_result = host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2dF(0, 20)).get());
            EXPECT_TRUE(scroll_result.did_scroll);
            EXPECT_TRUE(scroll_result.did_overscroll_root);
            EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0.000000f, 17.699997f),
                host_impl_->accumulated_root_overscroll());

            scroll_result = host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2dF(0.02f, -0.01f)).get());
            EXPECT_FALSE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_VECTOR2DF_EQ(gfx::Vector2dF(0.000000f, 17.699997f),
                host_impl_->accumulated_root_overscroll());
            host_impl_->ScrollEnd(EndState().get());
            // TestCase to check  kEpsilon, which prevents minute values to trigger
            // gloweffect without reaching edge.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                        InputHandler::WHEEL)
                    .thread);
            scroll_result = host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2dF(-0.12f, 0.1f)).get());
            EXPECT_FALSE(scroll_result.did_scroll);
            EXPECT_FALSE(scroll_result.did_overscroll_root);
            EXPECT_EQ(gfx::Vector2dF().ToString(),
                host_impl_->accumulated_root_overscroll().ToString());
            host_impl_->ScrollEnd(EndState().get());
        }
    }

    TEST_F(LayerTreeHostImplTest, OverscrollOnMainThread)
    {
        InputHandlerScrollResult scroll_result;
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());

        const gfx::Size content_size(50, 50);
        const gfx::Size viewport_size(50, 50);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        host_impl_->active_tree()
            ->InnerViewportScrollLayer()
            ->set_main_thread_scrolling_reasons(
                MainThreadScrollingReason::kThreadedScrollingDisabled);
        host_impl_->active_tree()
            ->OuterViewportScrollLayer()
            ->set_main_thread_scrolling_reasons(
                MainThreadScrollingReason::kThreadedScrollingDisabled);

        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        // Overscroll initiated outside layers will be handled by the main thread.
        EXPECT_EQ(nullptr, host_impl_->active_tree()->FindLayerThatIsHitByPoint(gfx::PointF(0, 60)));
        EXPECT_EQ(InputHandler::SCROLL_ON_MAIN_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(0, 60)).get(),
                    InputHandler::WHEEL)
                .thread);

        // Overscroll initiated inside layers will be handled by the main thread.
        EXPECT_NE(nullptr, host_impl_->active_tree()->FindLayerThatIsHitByPoint(gfx::PointF(0, 0)));
        EXPECT_EQ(
            InputHandler::SCROLL_ON_MAIN_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(0, 0)).get(), InputHandler::WHEEL)
                .thread);
    }

    // Test that scrolling the inner viewport directly works, as can happen when the
    // scroll chains up to it from an sibling of the outer viewport.
    TEST_F(LayerTreeHostImplTest, ScrollFromOuterViewportSibling)
    {
        const gfx::Size content_size(200, 200);
        const gfx::Size viewport_size(100, 100);

        LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();

        CreateBasicVirtualViewportLayers(viewport_size, viewport_size);
        host_impl_->active_tree()->set_top_controls_height(10);
        host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f);

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();

        LayerImpl* scroll_layer = nullptr;

        // Create a scrolling layer that's parented directly to the inner viewport.
        // This will test that scrolls that chain up to the inner viewport without
        // passing through the outer viewport still scroll correctly and affect
        // browser controls.
        {
            std::unique_ptr<LayerImpl> clip = LayerImpl::Create(layer_tree_impl, 10);
            clip->SetBounds(viewport_size);
            clip->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
            scroll->SetBounds(gfx::Size(400, 400));
            scroll->SetScrollClipLayer(clip->id());
            scroll->SetDrawsContent(true);

            scroll_layer = scroll.get();

            clip->test_properties()->AddChild(std::move(scroll));
            inner_scroll_layer->test_properties()->AddChild(std::move(clip));

            // Move the outer viewport layer away so that scrolls won't target it.
            host_impl_->active_tree()->OuterViewportContainerLayer()->SetPosition(
                gfx::PointF(400, 400));

            layer_tree_impl->BuildPropertyTreesForTesting();

            float min_page_scale = 1.f, max_page_scale = 4.f;
            float page_scale_factor = 2.f;
            host_impl_->active_tree()->PushPageScaleFromMainThread(
                page_scale_factor, min_page_scale, max_page_scale);
            host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
        }

        // Fully scroll the child.
        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(1000.f, 1000.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_EQ(1.f,
                host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(300.f, 300.f),
                scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                inner_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());
        }

        // Scrolling on the child now should chain up directly to the inner viewport.
        // Scrolling it should cause browser controls to hide. The outer viewport
        // should not be affected.
        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            gfx::Vector2d scroll_delta(0, 10);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            EXPECT_EQ(0.f,
                host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                inner_scroll_layer->CurrentScrollOffset());

            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 10.f),
                inner_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());
            host_impl_->ScrollEnd(EndState().get());
        }
    }

    // Test that scrolls chain correctly when a child scroller on the page (e.g. a
    // scrolling div) is set as the outer viewport. This happens in the
    // rootScroller proposal.
    TEST_F(LayerTreeHostImplTest, ScrollChainingWithReplacedOuterViewport)
    {
        const gfx::Size content_size(200, 200);
        const gfx::Size viewport_size(100, 100);

        LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();

        LayerImpl* content_layer = CreateBasicVirtualViewportLayers(viewport_size, content_size);
        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();

        LayerImpl* scroll_layer = nullptr;
        LayerImpl* child_scroll_layer = nullptr;

        // Initialization: Add two nested scrolling layers, simulating a scrolling div
        // with another scrolling div inside it. Set the outer "div" to be the outer
        // viewport.
        {
            std::unique_ptr<LayerImpl> clip = LayerImpl::Create(layer_tree_impl, 10);
            clip->SetBounds(content_size);
            clip->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
            scroll->SetBounds(gfx::Size(400, 400));
            scroll->SetScrollClipLayer(clip->id());
            scroll->SetDrawsContent(true);

            std::unique_ptr<LayerImpl> clip2 = LayerImpl::Create(layer_tree_impl, 12);
            clip2->SetBounds(gfx::Size(300, 300));
            clip2->SetPosition(gfx::PointF());
            clip2->SetDrawsContent(true);

            std::unique_ptr<LayerImpl> scroll2 = LayerImpl::Create(layer_tree_impl, 13);
            scroll2->SetBounds(gfx::Size(500, 500));
            scroll2->SetScrollClipLayer(clip2->id());
            scroll2->SetDrawsContent(true);

            scroll_layer = scroll.get();
            child_scroll_layer = scroll2.get();

            clip2->test_properties()->AddChild(std::move(scroll2));
            scroll->test_properties()->AddChild(std::move(clip2));

            clip->test_properties()->AddChild(std::move(scroll));
            content_layer->test_properties()->AddChild(std::move(clip));
            layer_tree_impl->SetViewportLayersFromIds(
                Layer::INVALID_ID, layer_tree_impl->PageScaleLayer()->id(),
                inner_scroll_layer->id(), scroll_layer->id());
            layer_tree_impl->BuildPropertyTreesForTesting();
        }

        // Scroll should target the nested scrolling layer in the content and then
        // chain to the parent scrolling layer which is now set as the outer
        // viewport. The original outer viewport layer shouldn't get any scroll here.
        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(200.f, 200.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(200.f, 200.f),
                child_scroll_layer->CurrentScrollOffset());

            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(200.f, 200.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 0.f),
                outer_scroll_layer->CurrentScrollOffset());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(200.f, 200.f),
                scroll_layer->CurrentScrollOffset());
        }

        // Now that the nested scrolling layers are fully scrolled, further scrolls
        // would normally chain up to the "outer viewport" but since we've set the
        // scrolling content as the outer viewport, it should stop chaining there.
        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100.f, 100.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());
        }

        // Zoom into the page by a 2X factor so that the inner viewport becomes
        // scrollable.
        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 2.f;
        host_impl_->active_tree()->PushPageScaleFromMainThread(
            page_scale_factor, min_page_scale, max_page_scale);
        host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);

        // Reset the parent scrolling layer (i.e. the current outer viewport) so that
        // we can ensure viewport scrolling works correctly.
        scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset(0, 0));

        // Scrolling the content layer should now scroll the inner viewport first,
        // and then chain up to the current outer viewport (i.e. the parent scroll
        // layer).
        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100.f, 100.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(50.f, 50.f),
                inner_scroll_layer->CurrentScrollOffset());

            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100.f, 100.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(0.f, 0.f),
                outer_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(50.f, 50.f),
                scroll_layer->CurrentScrollOffset());
        }
    }
    // Test that scrolls chain correctly when a child scroller on the page (e.g. a
    // scrolling div) is set as the outer viewport but scrolls start from a layer
    // that's not a descendant of the outer viewport. This happens in the
    // rootScroller proposal.
    TEST_F(LayerTreeHostImplTest, RootScrollerScrollNonDescendant)
    {
        const gfx::Size content_size(300, 300);
        const gfx::Size viewport_size(300, 300);

        LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();

        LayerImpl* content_layer = CreateBasicVirtualViewportLayers(viewport_size, content_size);
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();

        LayerImpl* outer_scroll_layer = nullptr;
        LayerImpl* sibling_scroll_layer = nullptr;

        // Initialization: Add a scrolling layer, simulating an ordinary DIV, to be
        // set as the outer viewport. Add a sibling scrolling layer that isn't a child
        // of the outer viewport scroll layer.
        {
            std::unique_ptr<LayerImpl> clip = LayerImpl::Create(layer_tree_impl, 10);
            clip->SetBounds(content_size);
            clip->SetPosition(gfx::PointF(100, 100));

            std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
            scroll->SetBounds(gfx::Size(1200, 1200));
            scroll->SetScrollClipLayer(clip->id());
            scroll->SetDrawsContent(true);

            outer_scroll_layer = scroll.get();

            clip->test_properties()->AddChild(std::move(scroll));
            content_layer->test_properties()->AddChild(std::move(clip));

            // Create the non-descendant.
            std::unique_ptr<LayerImpl> clip2 = LayerImpl::Create(layer_tree_impl, 14);
            clip2->SetBounds(gfx::Size(600, 600));
            clip2->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> scroll2 = LayerImpl::Create(layer_tree_impl, 15);
            scroll2->SetBounds(gfx::Size(1200, 1200));
            scroll2->SetScrollClipLayer(clip2->id());
            scroll2->SetDrawsContent(true);

            sibling_scroll_layer = scroll2.get();

            clip2->test_properties()->AddChild(std::move(scroll2));
            content_layer->test_properties()->AddChild(std::move(clip2));

            layer_tree_impl->SetViewportLayersFromIds(
                Layer::INVALID_ID, layer_tree_impl->PageScaleLayer()->id(),
                inner_scroll_layer->id(), outer_scroll_layer->id());
            layer_tree_impl->BuildPropertyTreesForTesting();

            ASSERT_EQ(outer_scroll_layer, layer_tree_impl->OuterViewportScrollLayer());
        }

        // Scrolls should target the non-descendant scroller. Chaining should not
        // propagate to the outer viewport scroll layer.
        {
            // This should fully scroll the layer.
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(1000.f, 1000.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(600.f, 600.f),
                sibling_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());

            // Scrolling now should chain up but, since the outer viewport is a sibling
            // rather than an ancestor, we shouldn't chain to it.
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(1000.f, 1000.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(600.f, 600.f),
                sibling_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());
        }

        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 1.f;
        host_impl_->active_tree()->PushPageScaleFromMainThread(
            page_scale_factor, min_page_scale, max_page_scale);

        gfx::Vector2dF viewport_size_vec(viewport_size.width(),
            viewport_size.height());

        // Reset the scroll offset.
        sibling_scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset());

        // Now pinch-zoom in. Anchoring should cause scrolling only on the inner
        // viewport layer.
        {
            // Pinch in to the middle of the screen. The inner viewport should scroll
            // to keep the gesture anchored but not the outer or the sibling scroller.
            page_scale_factor = 2.f;
            gfx::Point anchor(viewport_size.width() / 2, viewport_size.height() / 2);
            host_impl_->ScrollBegin(BeginState(anchor).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_factor, anchor);
            host_impl_->PinchGestureEnd();

            EXPECT_VECTOR_EQ(gfx::Vector2dF(anchor.x() / 2, anchor.y() / 2),
                inner_scroll_layer->CurrentScrollOffset());

            host_impl_->ScrollBy(UpdateState(anchor, viewport_size_vec).get());

            EXPECT_VECTOR_EQ(ScaleVector2d(viewport_size_vec, 1.f / page_scale_factor),
                inner_scroll_layer->CurrentScrollOffset());
            // TODO(bokan): This doesn't yet work but we'll probably want to fix this
            // at some point.
            // EXPECT_VECTOR_EQ(
            //     gfx::Vector2dF(),
            //     outer_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                sibling_scroll_layer->CurrentScrollOffset());

            host_impl_->ScrollEnd(EndState().get());
        }

        // Reset the scroll offsets
        sibling_scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset());
        inner_scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset());
        outer_scroll_layer->SetCurrentScrollOffset(gfx::ScrollOffset());

        // Scrolls over the sibling while pinched in should scroll the sibling first,
        // but then chain up to the inner viewport so that the user can still pan
        // around. The outer viewport should be unaffected.
        {
            // This should fully scroll the sibling but, because we latch to the
            // scroller, it shouldn't chain up to the inner viewport yet.
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(2000.f, 2000.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(600.f, 600.f),
                sibling_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                inner_scroll_layer->CurrentScrollOffset());

            // Scrolling now should chain up to the inner viewport.
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(2000.f, 2000.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(ScaleVector2d(viewport_size_vec, 1 / page_scale_factor),
                inner_scroll_layer->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());

            // No more scrolling should be possible.
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(2000.f, 2000.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(),
                outer_scroll_layer->CurrentScrollOffset());
        }
    }

    TEST_F(LayerTreeHostImplTest, OverscrollOnImplThread)
    {
        InputHandlerScrollResult scroll_result;
        LayerTreeSettings settings = DefaultSettings();
        CreateHostImpl(settings, CreateCompositorFrameSink());

        const gfx::Size content_size(50, 50);
        const gfx::Size viewport_size(50, 50);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        // By default, no main thread scrolling reasons should exist.
        LayerImpl* scroll_layer = host_impl_->active_tree()->InnerViewportScrollLayer();
        EXPECT_EQ(MainThreadScrollingReason::kNotScrollingOnMain,
            scroll_layer->main_thread_scrolling_reasons());

        DrawFrame();

        // Overscroll initiated outside layers will be handled by the impl thread.
        EXPECT_EQ(nullptr, host_impl_->active_tree()->FindLayerThatIsHitByPoint(gfx::PointF(0, 60)));
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(0, 60)).get(),
                    InputHandler::WHEEL)
                .thread);

        // Overscroll initiated inside layers will be handled by the impl thread.
        EXPECT_NE(nullptr, host_impl_->active_tree()->FindLayerThatIsHitByPoint(gfx::PointF(0, 0)));
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(0, 0)).get(), InputHandler::WHEEL)
                .thread);
    }

    class BlendStateCheckLayer : public LayerImpl {
    public:
        static std::unique_ptr<LayerImpl> Create(
            LayerTreeImpl* tree_impl,
            int id,
            ResourceProvider* resource_provider)
        {
            return base::WrapUnique(
                new BlendStateCheckLayer(tree_impl, id, resource_provider));
        }

        void AppendQuads(RenderPass* render_pass,
            AppendQuadsData* append_quads_data) override
        {
            quads_appended_ = true;

            gfx::Rect opaque_rect;
            if (contents_opaque())
                opaque_rect = quad_rect_;
            else
                opaque_rect = opaque_content_rect_;
            gfx::Rect visible_quad_rect = quad_rect_;

            SharedQuadState* shared_quad_state = render_pass->CreateAndAppendSharedQuadState();
            PopulateSharedQuadState(shared_quad_state);

            TileDrawQuad* test_blending_draw_quad = render_pass->CreateAndAppendDrawQuad<TileDrawQuad>();
            test_blending_draw_quad->SetNew(shared_quad_state, quad_rect_, opaque_rect,
                visible_quad_rect, resource_id_,
                gfx::RectF(0.f, 0.f, 1.f, 1.f),
                gfx::Size(1, 1), false, false);
            test_blending_draw_quad->visible_rect = quad_visible_rect_;
            EXPECT_EQ(blend_, test_blending_draw_quad->ShouldDrawWithBlending());
            EXPECT_EQ(has_render_surface_, !!render_surface());
        }

        void SetExpectation(bool blend, bool has_render_surface)
        {
            blend_ = blend;
            has_render_surface_ = has_render_surface;
            quads_appended_ = false;
        }

        bool quads_appended() const { return quads_appended_; }

        void SetQuadRect(const gfx::Rect& rect) { quad_rect_ = rect; }
        void SetQuadVisibleRect(const gfx::Rect& rect) { quad_visible_rect_ = rect; }
        void SetOpaqueContentRect(const gfx::Rect& rect)
        {
            opaque_content_rect_ = rect;
        }

    private:
        BlendStateCheckLayer(LayerTreeImpl* tree_impl,
            int id,
            ResourceProvider* resource_provider)
            : LayerImpl(tree_impl, id)
            , blend_(false)
            , has_render_surface_(false)
            , quads_appended_(false)
            , quad_rect_(5, 5, 5, 5)
            , quad_visible_rect_(5, 5, 5, 5)
            , resource_id_(resource_provider->CreateResource(
                  gfx::Size(1, 1),
                  ResourceProvider::TEXTURE_HINT_IMMUTABLE,
                  RGBA_8888,
                  gfx::ColorSpace()))
        {
            resource_provider->AllocateForTesting(resource_id_);
            SetBounds(gfx::Size(10, 10));
            SetDrawsContent(true);
        }

        bool blend_;
        bool has_render_surface_;
        bool quads_appended_;
        gfx::Rect quad_rect_;
        gfx::Rect opaque_content_rect_;
        gfx::Rect quad_visible_rect_;
        ResourceId resource_id_;
    };

    TEST_F(LayerTreeHostImplTest, BlendingOffWhenDrawingOpaqueLayers)
    {
        {
            std::unique_ptr<LayerImpl> root = LayerImpl::Create(host_impl_->active_tree(), 1);
            root->SetBounds(gfx::Size(10, 10));
            root->SetDrawsContent(false);
            root->test_properties()->force_render_surface = true;
            host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        }
        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();

        root->test_properties()->AddChild(BlendStateCheckLayer::Create(
            host_impl_->active_tree(), 2, host_impl_->resource_provider()));
        BlendStateCheckLayer* layer1 = static_cast<BlendStateCheckLayer*>(root->test_properties()->children[0]);
        layer1->SetPosition(gfx::PointF(2.f, 2.f));

        LayerTreeHostImpl::FrameData frame;

        // Opaque layer, drawn without blending.
        layer1->SetContentsOpaque(true);
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with translucent content and painting, so drawn with blending.
        layer1->SetContentsOpaque(false);
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with translucent opacity, drawn with blending.
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 0.5f;
        layer1->NoteLayerPropertyChanged();
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with translucent opacity and painting, drawn with blending.
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 0.5f;
        layer1->NoteLayerPropertyChanged();
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        layer1->test_properties()->AddChild(BlendStateCheckLayer::Create(
            host_impl_->active_tree(), 3, host_impl_->resource_provider()));
        BlendStateCheckLayer* layer2 = static_cast<BlendStateCheckLayer*>(
            layer1->test_properties()->children[0]);
        layer2->SetPosition(gfx::PointF(4.f, 4.f));

        // 2 opaque layers, drawn without blending.
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 1.f;
        layer1->NoteLayerPropertyChanged();
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetContentsOpaque(true);
        layer2->test_properties()->opacity = 1.f;
        layer2->NoteLayerPropertyChanged();
        layer2->SetExpectation(false, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Parent layer with translucent content, drawn with blending.
        // Child layer with opaque content, drawn without blending.
        layer1->SetContentsOpaque(false);
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetExpectation(false, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Parent layer with translucent content but opaque painting, drawn without
        // blending.
        // Child layer with opaque content, drawn without blending.
        layer1->SetContentsOpaque(true);
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetExpectation(false, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Parent layer with translucent opacity and opaque content. Since it has a
        // drawing child, it's drawn to a render surface which carries the opacity,
        // so it's itself drawn without blending.
        // Child layer with opaque content, drawn without blending (parent surface
        // carries the inherited opacity).
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 0.5f;
        layer1->NoteLayerPropertyChanged();
        layer1->test_properties()->force_render_surface = true;
        layer1->SetExpectation(false, true);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetExpectation(false, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        FakeLayerTreeHostImpl::RecursiveUpdateNumChildren(
            host_impl_->active_tree()->root_layer_for_testing());
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);
        layer1->test_properties()->force_render_surface = false;

        // Draw again, but with child non-opaque, to make sure
        // layer1 not culled.
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 1.f;
        layer1->NoteLayerPropertyChanged();
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetContentsOpaque(true);
        layer2->test_properties()->opacity = 0.5f;
        layer2->NoteLayerPropertyChanged();
        layer2->SetExpectation(true, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // A second way of making the child non-opaque.
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 1.f;
        layer1->NoteLayerPropertyChanged();
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetContentsOpaque(false);
        layer2->test_properties()->opacity = 1.f;
        layer2->NoteLayerPropertyChanged();
        layer2->SetExpectation(true, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // And when the layer says its not opaque but is painted opaque, it is not
        // blended.
        layer1->SetContentsOpaque(true);
        layer1->test_properties()->opacity = 1.f;
        layer1->NoteLayerPropertyChanged();
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        layer2->SetContentsOpaque(true);
        layer2->test_properties()->opacity = 1.f;
        layer2->NoteLayerPropertyChanged();
        layer2->SetExpectation(false, false);
        layer2->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        EXPECT_TRUE(layer2->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with partially opaque contents, drawn with blending.
        layer1->SetContentsOpaque(false);
        layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
        layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 5, 5));
        layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with partially opaque contents partially culled, drawn with blending.
        layer1->SetContentsOpaque(false);
        layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
        layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 5, 2));
        layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with partially opaque contents culled, drawn with blending.
        layer1->SetContentsOpaque(false);
        layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
        layer1->SetQuadVisibleRect(gfx::Rect(7, 5, 3, 5));
        layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
        layer1->SetExpectation(true, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);

        // Layer with partially opaque contents and translucent contents culled, drawn
        // without blending.
        layer1->SetContentsOpaque(false);
        layer1->SetQuadRect(gfx::Rect(5, 5, 5, 5));
        layer1->SetQuadVisibleRect(gfx::Rect(5, 5, 2, 5));
        layer1->SetOpaqueContentRect(gfx::Rect(5, 5, 2, 5));
        layer1->SetExpectation(false, false);
        layer1->SetUpdateRect(gfx::Rect(layer1->bounds()));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        EXPECT_TRUE(layer1->quads_appended());
        host_impl_->DidDrawAllLayers(frame);
    }

    static bool MayContainVideoBitSetOnFrameData(LayerTreeHostImpl* host_impl)
    {
        host_impl->active_tree()->BuildPropertyTreesForTesting();
        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl->PrepareToDraw(&frame));
        host_impl->DrawLayers(&frame);
        host_impl->DidDrawAllLayers(frame);
        return frame.may_contain_video;
    }

    TEST_F(LayerTreeHostImplTest, MayContainVideo)
    {
        gfx::Size big_size(1000, 1000);
        host_impl_->SetViewportSize(big_size);

        int layer_id = 1;
        host_impl_->active_tree()->SetRootLayerForTesting(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), layer_id++));
        DidDrawCheckLayer* root = static_cast<DidDrawCheckLayer*>(*host_impl_->active_tree()->begin());

        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), layer_id++));
        DidDrawCheckLayer* video_layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children.back());
        video_layer->set_may_contain_video(true);
        EXPECT_TRUE(MayContainVideoBitSetOnFrameData(host_impl_.get()));

        // Test with the video layer occluded.
        root->test_properties()->AddChild(
            DidDrawCheckLayer::Create(host_impl_->active_tree(), layer_id++));
        DidDrawCheckLayer* large_layer = static_cast<DidDrawCheckLayer*>(root->test_properties()->children.back());
        large_layer->SetBounds(big_size);
        large_layer->SetContentsOpaque(true);
        EXPECT_FALSE(MayContainVideoBitSetOnFrameData(host_impl_.get()));

        // Remove the large layer.
        root->test_properties()->RemoveChild(large_layer);
        EXPECT_TRUE(MayContainVideoBitSetOnFrameData(host_impl_.get()));

        // Move the video layer so it goes beyond the root.
        video_layer->SetPosition(gfx::PointF(100.f, 100.f));
        EXPECT_FALSE(MayContainVideoBitSetOnFrameData(host_impl_.get()));

        video_layer->SetPosition(gfx::PointF(0.f, 0.f));
        video_layer->NoteLayerPropertyChanged();
        EXPECT_TRUE(MayContainVideoBitSetOnFrameData(host_impl_.get()));
    }

    class LayerTreeHostImplViewportCoveredTest : public LayerTreeHostImplTest {
    protected:
        LayerTreeHostImplViewportCoveredTest()
            : gutter_quad_material_(DrawQuad::SOLID_COLOR)
            , child_(NULL)
            , did_activate_pending_tree_(false)
        {
        }

        std::unique_ptr<CompositorFrameSink> CreateFakeCompositorFrameSink(
            bool software)
        {
            if (software)
                return FakeCompositorFrameSink::CreateSoftware();
            return FakeCompositorFrameSink::Create3d();
        }

        void SetupActiveTreeLayers()
        {
            host_impl_->active_tree()->set_background_color(SK_ColorGRAY);
            host_impl_->active_tree()->SetRootLayerForTesting(
                LayerImpl::Create(host_impl_->active_tree(), 1));
            host_impl_->active_tree()
                ->root_layer_for_testing()
                ->test_properties()
                ->force_render_surface
                = true;
            host_impl_->active_tree()
                ->root_layer_for_testing()
                ->test_properties()
                ->AddChild(BlendStateCheckLayer::Create(
                    host_impl_->active_tree(), 2, host_impl_->resource_provider()));
            child_ = static_cast<BlendStateCheckLayer*>(host_impl_->active_tree()
                                                            ->root_layer_for_testing()
                                                            ->test_properties()
                                                            ->children[0]);
            child_->SetExpectation(false, false);
            child_->SetContentsOpaque(true);
        }

        // Expect no gutter rects.
        void TestLayerCoversFullViewport()
        {
            gfx::Rect layer_rect(viewport_size_);
            child_->SetPosition(gfx::PointF(layer_rect.origin()));
            child_->SetBounds(layer_rect.size());
            child_->SetQuadRect(gfx::Rect(layer_rect.size()));
            child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
            host_impl_->active_tree()->BuildPropertyTreesForTesting();

            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            ASSERT_EQ(1u, frame.render_passes.size());

            EXPECT_EQ(0u, CountGutterQuads(frame.render_passes[0]->quad_list));
            EXPECT_EQ(1u, frame.render_passes[0]->quad_list.size());
            ValidateTextureDrawQuads(frame.render_passes[0]->quad_list);

            VerifyQuadsExactlyCoverViewport(frame.render_passes[0]->quad_list);
            host_impl_->DidDrawAllLayers(frame);
        }

        // Expect fullscreen gutter rect.
        void SetUpEmptylayer()
        {
            gfx::Rect layer_rect(0, 0, 0, 0);
            child_->SetPosition(gfx::PointF(layer_rect.origin()));
            child_->SetBounds(layer_rect.size());
            child_->SetQuadRect(gfx::Rect(layer_rect.size()));
            child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
            host_impl_->active_tree()->BuildPropertyTreesForTesting();
        }

        void VerifyEmptyLayerRenderPasses(const RenderPassList& render_passes)
        {
            ASSERT_EQ(1u, render_passes.size());

            EXPECT_EQ(1u, CountGutterQuads(render_passes[0]->quad_list));
            EXPECT_EQ(1u, render_passes[0]->quad_list.size());
            ValidateTextureDrawQuads(render_passes[0]->quad_list);

            VerifyQuadsExactlyCoverViewport(render_passes[0]->quad_list);
        }

        void TestEmptyLayer()
        {
            SetUpEmptylayer();
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            VerifyEmptyLayerRenderPasses(frame.render_passes);
            host_impl_->DidDrawAllLayers(frame);
        }

        void TestEmptyLayerWithOnDraw()
        {
            SetUpEmptylayer();
            gfx::Transform identity;
            gfx::Rect viewport(viewport_size_);
            bool resourceless_software_draw = true;
            host_impl_->OnDraw(identity, viewport, resourceless_software_draw);
            VerifyEmptyLayerRenderPasses(last_on_draw_render_passes_);
        }

        // Expect four surrounding gutter rects.
        void SetUpLayerInMiddleOfViewport()
        {
            gfx::Rect layer_rect(500, 500, 200, 200);
            child_->SetPosition(gfx::PointF(layer_rect.origin()));
            child_->SetBounds(layer_rect.size());
            child_->SetQuadRect(gfx::Rect(layer_rect.size()));
            child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
            host_impl_->active_tree()->BuildPropertyTreesForTesting();
        }

        void VerifyLayerInMiddleOfViewport(const RenderPassList& render_passes)
        {
            ASSERT_EQ(1u, render_passes.size());

            EXPECT_EQ(4u, CountGutterQuads(render_passes[0]->quad_list));
            EXPECT_EQ(5u, render_passes[0]->quad_list.size());
            ValidateTextureDrawQuads(render_passes[0]->quad_list);

            VerifyQuadsExactlyCoverViewport(render_passes[0]->quad_list);
        }

        void TestLayerInMiddleOfViewport()
        {
            SetUpLayerInMiddleOfViewport();
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            VerifyLayerInMiddleOfViewport(frame.render_passes);
            host_impl_->DidDrawAllLayers(frame);
        }

        void TestLayerInMiddleOfViewportWithOnDraw()
        {
            SetUpLayerInMiddleOfViewport();
            gfx::Transform identity;
            gfx::Rect viewport(viewport_size_);
            bool resourceless_software_draw = true;
            host_impl_->OnDraw(identity, viewport, resourceless_software_draw);
            VerifyLayerInMiddleOfViewport(last_on_draw_render_passes_);
        }

        // Expect no gutter rects.
        void SetUpLayerIsLargerThanViewport()
        {
            gfx::Rect layer_rect(viewport_size_.width() + 10,
                viewport_size_.height() + 10);
            child_->SetPosition(gfx::PointF(layer_rect.origin()));
            child_->SetBounds(layer_rect.size());
            child_->SetQuadRect(gfx::Rect(layer_rect.size()));
            child_->SetQuadVisibleRect(gfx::Rect(layer_rect.size()));
            host_impl_->active_tree()->BuildPropertyTreesForTesting();
        }

        void VerifyLayerIsLargerThanViewport(const RenderPassList& render_passes)
        {
            ASSERT_EQ(1u, render_passes.size());

            EXPECT_EQ(0u, CountGutterQuads(render_passes[0]->quad_list));
            EXPECT_EQ(1u, render_passes[0]->quad_list.size());
            ValidateTextureDrawQuads(render_passes[0]->quad_list);
        }

        void TestLayerIsLargerThanViewport()
        {
            SetUpLayerIsLargerThanViewport();
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            VerifyLayerIsLargerThanViewport(frame.render_passes);
            host_impl_->DidDrawAllLayers(frame);
        }

        void TestLayerIsLargerThanViewportWithOnDraw()
        {
            SetUpLayerIsLargerThanViewport();
            gfx::Transform identity;
            gfx::Rect viewport(viewport_size_);
            bool resourceless_software_draw = true;
            host_impl_->OnDraw(identity, viewport, resourceless_software_draw);
            VerifyLayerIsLargerThanViewport(last_on_draw_render_passes_);
        }

        void DidActivateSyncTree() override { did_activate_pending_tree_ = true; }

        void set_gutter_quad_material(DrawQuad::Material material)
        {
            gutter_quad_material_ = material;
        }
        void set_gutter_texture_size(const gfx::Size& gutter_texture_size)
        {
            gutter_texture_size_ = gutter_texture_size;
        }

    protected:
        size_t CountGutterQuads(const QuadList& quad_list)
        {
            size_t num_gutter_quads = 0;
            for (auto* quad : quad_list) {
                num_gutter_quads += (quad->material == gutter_quad_material_) ? 1 : 0;
            }
            return num_gutter_quads;
        }

        void VerifyQuadsExactlyCoverViewport(const QuadList& quad_list)
        {
            LayerTestCommon::VerifyQuadsExactlyCoverRect(
                quad_list, gfx::Rect(DipSizeToPixelSize(viewport_size_)));
        }

        // Make sure that the texture coordinates match their expectations.
        void ValidateTextureDrawQuads(const QuadList& quad_list)
        {
            for (auto* quad : quad_list) {
                if (quad->material != DrawQuad::TEXTURE_CONTENT)
                    continue;
                const TextureDrawQuad* texture_quad = TextureDrawQuad::MaterialCast(quad);
                gfx::SizeF gutter_texture_size_pixels = gfx::ScaleSize(gfx::SizeF(gutter_texture_size_),
                    host_impl_->active_tree()->device_scale_factor());
                EXPECT_EQ(texture_quad->uv_top_left.x(),
                    texture_quad->rect.x() / gutter_texture_size_pixels.width());
                EXPECT_EQ(texture_quad->uv_top_left.y(),
                    texture_quad->rect.y() / gutter_texture_size_pixels.height());
                EXPECT_EQ(
                    texture_quad->uv_bottom_right.x(),
                    texture_quad->rect.right() / gutter_texture_size_pixels.width());
                EXPECT_EQ(
                    texture_quad->uv_bottom_right.y(),
                    texture_quad->rect.bottom() / gutter_texture_size_pixels.height());
            }
        }

        gfx::Size DipSizeToPixelSize(const gfx::Size& size)
        {
            return gfx::ScaleToRoundedSize(
                size, host_impl_->active_tree()->device_scale_factor());
        }

        DrawQuad::Material gutter_quad_material_;
        gfx::Size gutter_texture_size_;
        gfx::Size viewport_size_;
        BlendStateCheckLayer* child_;
        bool did_activate_pending_tree_;
    };

    TEST_F(LayerTreeHostImplViewportCoveredTest, ViewportCovered)
    {
        viewport_size_ = gfx::Size(1000, 1000);

        bool software = false;
        CreateHostImpl(DefaultSettings(), CreateFakeCompositorFrameSink(software));

        host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_));
        SetupActiveTreeLayers();
        EXPECT_SCOPED(TestLayerCoversFullViewport());
        EXPECT_SCOPED(TestEmptyLayer());
        EXPECT_SCOPED(TestLayerInMiddleOfViewport());
        EXPECT_SCOPED(TestLayerIsLargerThanViewport());
    }

    TEST_F(LayerTreeHostImplViewportCoveredTest, ViewportCoveredScaled)
    {
        viewport_size_ = gfx::Size(1000, 1000);

        bool software = false;
        CreateHostImpl(DefaultSettings(), CreateFakeCompositorFrameSink(software));

        host_impl_->active_tree()->SetDeviceScaleFactor(2.f);
        host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_));
        SetupActiveTreeLayers();
        EXPECT_SCOPED(TestLayerCoversFullViewport());
        EXPECT_SCOPED(TestEmptyLayer());
        EXPECT_SCOPED(TestLayerInMiddleOfViewport());
        EXPECT_SCOPED(TestLayerIsLargerThanViewport());
    }

    TEST_F(LayerTreeHostImplViewportCoveredTest, ActiveTreeGrowViewportInvalid)
    {
        viewport_size_ = gfx::Size(1000, 1000);

        bool software = true;
        CreateHostImpl(DefaultSettings(), CreateFakeCompositorFrameSink(software));

        // Pending tree to force active_tree size invalid. Not used otherwise.
        host_impl_->CreatePendingTree();
        host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_));
        EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid());

        SetupActiveTreeLayers();
        EXPECT_SCOPED(TestEmptyLayerWithOnDraw());
        EXPECT_SCOPED(TestLayerInMiddleOfViewportWithOnDraw());
        EXPECT_SCOPED(TestLayerIsLargerThanViewportWithOnDraw());
    }

    TEST_F(LayerTreeHostImplViewportCoveredTest, ActiveTreeShrinkViewportInvalid)
    {
        viewport_size_ = gfx::Size(1000, 1000);

        bool software = true;
        CreateHostImpl(DefaultSettings(), CreateFakeCompositorFrameSink(software));

        // Set larger viewport and activate it to active tree.
        host_impl_->CreatePendingTree();
        gfx::Size larger_viewport(viewport_size_.width() + 100,
            viewport_size_.height() + 100);
        host_impl_->SetViewportSize(DipSizeToPixelSize(larger_viewport));
        EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid());
        host_impl_->ActivateSyncTree();
        EXPECT_TRUE(did_activate_pending_tree_);
        EXPECT_FALSE(host_impl_->active_tree()->ViewportSizeInvalid());

        // Shrink pending tree viewport without activating.
        host_impl_->CreatePendingTree();
        host_impl_->SetViewportSize(DipSizeToPixelSize(viewport_size_));
        EXPECT_TRUE(host_impl_->active_tree()->ViewportSizeInvalid());

        SetupActiveTreeLayers();
        EXPECT_SCOPED(TestEmptyLayerWithOnDraw());
        EXPECT_SCOPED(TestLayerInMiddleOfViewportWithOnDraw());
        EXPECT_SCOPED(TestLayerIsLargerThanViewportWithOnDraw());
    }

    class FakeDrawableLayerImpl : public LayerImpl {
    public:
        static std::unique_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id)
        {
            return base::WrapUnique(new FakeDrawableLayerImpl(tree_impl, id));
        }

    protected:
        FakeDrawableLayerImpl(LayerTreeImpl* tree_impl, int id)
            : LayerImpl(tree_impl, id)
        {
        }
    };

    // Make sure damage tracking propagates all the way to the graphics context,
    // where it should request to swap only the sub-buffer that is damaged.
    TEST_F(LayerTreeHostImplTest, PartialSwapReceivesDamageRect)
    {
        scoped_refptr<TestContextProvider> context_provider(
            TestContextProvider::Create());
        context_provider->BindToCurrentThread();
        context_provider->TestContext3d()->set_have_post_sub_buffer(true);

        std::unique_ptr<FakeCompositorFrameSink> compositor_frame_sink(
            FakeCompositorFrameSink::Create3d(context_provider));
        FakeCompositorFrameSink* fake_compositor_frame_sink = compositor_frame_sink.get();

        // This test creates its own LayerTreeHostImpl, so
        // that we can force partial swap enabled.
        LayerTreeSettings settings = DefaultSettings();
        settings.renderer_settings.partial_swap_enabled = true;
        std::unique_ptr<LayerTreeHostImpl> layer_tree_host_impl = LayerTreeHostImpl::Create(
            settings, this, &task_runner_provider_, &stats_instrumentation_,
            &task_graph_runner_,
            AnimationHost::CreateForTesting(ThreadInstance::IMPL), 0, nullptr);
        layer_tree_host_impl->SetVisible(true);
        layer_tree_host_impl->InitializeRenderer(compositor_frame_sink.get());
        layer_tree_host_impl->WillBeginImplFrame(
            CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2));
        layer_tree_host_impl->SetViewportSize(gfx::Size(500, 500));

        std::unique_ptr<LayerImpl> root = FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 1);
        root->test_properties()->force_render_surface = true;
        std::unique_ptr<LayerImpl> child = FakeDrawableLayerImpl::Create(layer_tree_host_impl->active_tree(), 2);
        child->SetPosition(gfx::PointF(12.f, 13.f));
        child->SetBounds(gfx::Size(14, 15));
        child->SetDrawsContent(true);
        root->SetBounds(gfx::Size(500, 500));
        root->SetDrawsContent(true);
        root->test_properties()->AddChild(std::move(child));
        layer_tree_host_impl->active_tree()->SetRootLayerForTesting(std::move(root));
        layer_tree_host_impl->active_tree()->BuildPropertyTreesForTesting();

        LayerTreeHostImpl::FrameData frame;

        // First frame, the entire screen should get swapped.
        EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame));
        layer_tree_host_impl->DrawLayers(&frame);
        layer_tree_host_impl->DidDrawAllLayers(frame);
        gfx::Rect expected_swap_rect(500, 500);
        EXPECT_EQ(expected_swap_rect.ToString(),
            fake_compositor_frame_sink->last_swap_rect().ToString());

        // Second frame, only the damaged area should get swapped. Damage should be
        // the union of old and new child rects: gfx::Rect(26, 28).
        layer_tree_host_impl->active_tree()
            ->root_layer_for_testing()
            ->test_properties()
            ->children[0]
            ->SetPosition(gfx::PointF());
        layer_tree_host_impl->active_tree()
            ->root_layer_for_testing()
            ->test_properties()
            ->children[0]
            ->NoteLayerPropertyChanged();
        layer_tree_host_impl->active_tree()->BuildPropertyTreesForTesting();
        EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame));
        layer_tree_host_impl->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        expected_swap_rect = gfx::Rect(26, 28);
        EXPECT_EQ(expected_swap_rect.ToString(),
            fake_compositor_frame_sink->last_swap_rect().ToString());

        layer_tree_host_impl->SetViewportSize(gfx::Size(10, 10));
        // This will damage everything.
        layer_tree_host_impl->active_tree()
            ->root_layer_for_testing()
            ->SetBackgroundColor(SK_ColorBLACK);
        EXPECT_EQ(DRAW_SUCCESS, layer_tree_host_impl->PrepareToDraw(&frame));
        layer_tree_host_impl->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        expected_swap_rect = gfx::Rect(10, 10);
        EXPECT_EQ(expected_swap_rect.ToString(),
            fake_compositor_frame_sink->last_swap_rect().ToString());

        layer_tree_host_impl->ReleaseCompositorFrameSink();
    }

    TEST_F(LayerTreeHostImplTest, RootLayerDoesntCreateExtraSurface)
    {
        std::unique_ptr<LayerImpl> root = FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 1);
        std::unique_ptr<LayerImpl> child = FakeDrawableLayerImpl::Create(host_impl_->active_tree(), 2);
        child->SetBounds(gfx::Size(10, 10));
        child->SetDrawsContent(true);
        root->SetBounds(gfx::Size(10, 10));
        root->SetDrawsContent(true);
        root->test_properties()->force_render_surface = true;
        root->test_properties()->AddChild(std::move(child));

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        LayerTreeHostImpl::FrameData frame;

        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        EXPECT_EQ(1u, frame.render_surface_layer_list->size());
        EXPECT_EQ(1u, frame.render_passes.size());
        host_impl_->DidDrawAllLayers(frame);
    }

    class FakeLayerWithQuads : public LayerImpl {
    public:
        static std::unique_ptr<LayerImpl> Create(LayerTreeImpl* tree_impl, int id)
        {
            return base::WrapUnique(new FakeLayerWithQuads(tree_impl, id));
        }

        void AppendQuads(RenderPass* render_pass,
            AppendQuadsData* append_quads_data) override
        {
            SharedQuadState* shared_quad_state = render_pass->CreateAndAppendSharedQuadState();
            PopulateSharedQuadState(shared_quad_state);

            SkColor gray = SkColorSetRGB(100, 100, 100);
            gfx::Rect quad_rect(bounds());
            gfx::Rect visible_quad_rect(quad_rect);
            SolidColorDrawQuad* my_quad = render_pass->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
            my_quad->SetNew(shared_quad_state, quad_rect, visible_quad_rect, gray,
                false);
        }

    private:
        FakeLayerWithQuads(LayerTreeImpl* tree_impl, int id)
            : LayerImpl(tree_impl, id)
        {
        }
    };

    static std::unique_ptr<LayerTreeHostImpl> SetupLayersForOpacity(
        LayerTreeSettings settings,
        bool partial_swap,
        LayerTreeHostImplClient* client,
        TaskRunnerProvider* task_runner_provider,
        TaskGraphRunner* task_graph_runner,
        RenderingStatsInstrumentation* stats_instrumentation,
        CompositorFrameSink* compositor_frame_sink)
    {
        settings.renderer_settings.partial_swap_enabled = partial_swap;
        std::unique_ptr<LayerTreeHostImpl> my_host_impl = LayerTreeHostImpl::Create(
            settings, client, task_runner_provider, stats_instrumentation,
            task_graph_runner, AnimationHost::CreateForTesting(ThreadInstance::IMPL),
            0, nullptr);
        my_host_impl->SetVisible(true);
        my_host_impl->InitializeRenderer(compositor_frame_sink);
        my_host_impl->WillBeginImplFrame(
            CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2));
        my_host_impl->SetViewportSize(gfx::Size(100, 100));

        /*
    Layers are created as follows:

    +--------------------+
    |                  1 |
    |  +-----------+     |
    |  |         2 |     |
    |  | +-------------------+
    |  | |   3               |
    |  | +-------------------+
    |  |           |     |
    |  +-----------+     |
    |                    |
    |                    |
    +--------------------+

    Layers 1, 2 have render surfaces
  */
        std::unique_ptr<LayerImpl> root = LayerImpl::Create(my_host_impl->active_tree(), 1);
        std::unique_ptr<LayerImpl> child = LayerImpl::Create(my_host_impl->active_tree(), 2);
        std::unique_ptr<LayerImpl> grand_child = FakeLayerWithQuads::Create(my_host_impl->active_tree(), 3);

        gfx::Rect root_rect(0, 0, 100, 100);
        gfx::Rect child_rect(10, 10, 50, 50);
        gfx::Rect grand_child_rect(5, 5, 150, 150);

        root->test_properties()->force_render_surface = true;
        root->SetPosition(gfx::PointF(root_rect.origin()));
        root->SetBounds(root_rect.size());
        root->draw_properties().visible_layer_rect = root_rect;
        root->SetDrawsContent(false);

        child->SetPosition(gfx::PointF(child_rect.x(), child_rect.y()));
        child->test_properties()->opacity = 0.5f;
        child->SetBounds(gfx::Size(child_rect.width(), child_rect.height()));
        child->draw_properties().visible_layer_rect = child_rect;
        child->SetDrawsContent(false);
        child->test_properties()->force_render_surface = true;

        grand_child->SetPosition(gfx::PointF(grand_child_rect.origin()));
        grand_child->SetBounds(grand_child_rect.size());
        grand_child->draw_properties().visible_layer_rect = grand_child_rect;
        grand_child->SetDrawsContent(true);

        child->test_properties()->AddChild(std::move(grand_child));
        root->test_properties()->AddChild(std::move(child));

        my_host_impl->active_tree()->SetRootLayerForTesting(std::move(root));
        my_host_impl->active_tree()->BuildPropertyTreesForTesting();
        return my_host_impl;
    }

    TEST_F(LayerTreeHostImplTest, ContributingLayerEmptyScissorPartialSwap)
    {
        TestTaskGraphRunner task_graph_runner;
        scoped_refptr<TestContextProvider> provider(TestContextProvider::Create());
        provider->BindToCurrentThread();
        provider->TestContext3d()->set_have_post_sub_buffer(true);
        std::unique_ptr<CompositorFrameSink> compositor_frame_sink(
            FakeCompositorFrameSink::Create3d(provider));
        std::unique_ptr<LayerTreeHostImpl> my_host_impl = SetupLayersForOpacity(
            DefaultSettings(), true, this, &task_runner_provider_, &task_graph_runner,
            &stats_instrumentation_, compositor_frame_sink.get());
        {
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, my_host_impl->PrepareToDraw(&frame));

            // Verify all quads have been computed
            ASSERT_EQ(2U, frame.render_passes.size());
            ASSERT_EQ(1U, frame.render_passes[0]->quad_list.size());
            ASSERT_EQ(1U, frame.render_passes[1]->quad_list.size());
            EXPECT_EQ(DrawQuad::SOLID_COLOR,
                frame.render_passes[0]->quad_list.front()->material);
            EXPECT_EQ(DrawQuad::RENDER_PASS,
                frame.render_passes[1]->quad_list.front()->material);

            my_host_impl->DrawLayers(&frame);
            my_host_impl->DidDrawAllLayers(frame);
        }
        my_host_impl->ReleaseCompositorFrameSink();
    }

    TEST_F(LayerTreeHostImplTest, ContributingLayerEmptyScissorNoPartialSwap)
    {
        TestTaskGraphRunner task_graph_runner;
        scoped_refptr<TestContextProvider> provider(TestContextProvider::Create());
        provider->BindToCurrentThread();
        provider->TestContext3d()->set_have_post_sub_buffer(true);
        std::unique_ptr<CompositorFrameSink> compositor_frame_sink(
            FakeCompositorFrameSink::Create3d(provider));
        std::unique_ptr<LayerTreeHostImpl> my_host_impl = SetupLayersForOpacity(
            DefaultSettings(), false, this, &task_runner_provider_,
            &task_graph_runner, &stats_instrumentation_, compositor_frame_sink.get());
        {
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, my_host_impl->PrepareToDraw(&frame));

            // Verify all quads have been computed
            ASSERT_EQ(2U, frame.render_passes.size());
            ASSERT_EQ(1U, frame.render_passes[0]->quad_list.size());
            ASSERT_EQ(1U, frame.render_passes[1]->quad_list.size());
            EXPECT_EQ(DrawQuad::SOLID_COLOR,
                frame.render_passes[0]->quad_list.front()->material);
            EXPECT_EQ(DrawQuad::RENDER_PASS,
                frame.render_passes[1]->quad_list.front()->material);

            my_host_impl->DrawLayers(&frame);
            my_host_impl->DidDrawAllLayers(frame);
        }
        my_host_impl->ReleaseCompositorFrameSink();
    }

    TEST_F(LayerTreeHostImplTest, LayersFreeTextures)
    {
        std::unique_ptr<TestWebGraphicsContext3D> context = TestWebGraphicsContext3D::Create();
        TestWebGraphicsContext3D* context3d = context.get();
        std::unique_ptr<CompositorFrameSink> compositor_frame_sink(
            FakeCompositorFrameSink::Create3d(std::move(context)));
        CreateHostImpl(DefaultSettings(), std::move(compositor_frame_sink));

        std::unique_ptr<LayerImpl> root_layer = LayerImpl::Create(host_impl_->active_tree(), 1);
        root_layer->SetBounds(gfx::Size(10, 10));
        root_layer->test_properties()->force_render_surface = true;

        scoped_refptr<VideoFrame> softwareFrame = media::VideoFrame::CreateColorFrame(
            gfx::Size(4, 4), 0x80, 0x80, 0x80, base::TimeDelta());
        FakeVideoFrameProvider provider;
        provider.set_frame(softwareFrame);
        std::unique_ptr<VideoLayerImpl> video_layer = VideoLayerImpl::Create(
            host_impl_->active_tree(), 4, &provider, media::VIDEO_ROTATION_0);
        video_layer->SetBounds(gfx::Size(10, 10));
        video_layer->SetDrawsContent(true);
        root_layer->test_properties()->AddChild(std::move(video_layer));

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_layer));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        EXPECT_EQ(0u, context3d->NumTextures());

        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        EXPECT_GT(context3d->NumTextures(), 0u);

        // Kill the layer tree.
        host_impl_->active_tree()->DetachLayers();
        // There should be no textures left in use after.
        EXPECT_EQ(0u, context3d->NumTextures());
    }

    class MockDrawQuadsToFillScreenContext : public TestWebGraphicsContext3D {
    public:
        MOCK_METHOD1(useProgram, void(GLuint program));
        MOCK_METHOD4(drawElements,
            void(GLenum mode, GLsizei count, GLenum type, GLintptr offset));
    };

    TEST_F(LayerTreeHostImplTest, HasTransparentBackground)
    {
        SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->active_tree()->set_background_color(SK_ColorWHITE);

        // Verify one quad is drawn when transparent background set is not set.
        host_impl_->active_tree()->set_has_transparent_background(false);
        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        {
            const auto& root_pass = frame.render_passes.back();
            ASSERT_EQ(1u, root_pass->quad_list.size());
            EXPECT_EQ(DrawQuad::SOLID_COLOR, root_pass->quad_list.front()->material);
        }
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        // Cause damage so we would draw something if possible.
        host_impl_->SetFullViewportDamage();

        // Verify no quads are drawn when transparent background is set.
        host_impl_->active_tree()->set_has_transparent_background(true);
        host_impl_->SetFullViewportDamage();
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        {
            const auto& root_pass = frame.render_passes.back();
            ASSERT_EQ(0u, root_pass->quad_list.size());
        }
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);
    }

    class LayerTreeHostImplTestDrawAndTestDamage : public LayerTreeHostImplTest {
    protected:
        std::unique_ptr<CompositorFrameSink> CreateCompositorFrameSink() override
        {
            return FakeCompositorFrameSink::Create3d();
        }

        void DrawFrameAndTestDamage(const gfx::Rect& expected_damage)
        {
            bool expect_to_draw = !expected_damage.IsEmpty();

            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));

            if (!expect_to_draw) {
                // With no damage, we don't draw, and no quads are created.
                ASSERT_EQ(0u, frame.render_passes.size());
            } else {
                ASSERT_EQ(1u, frame.render_passes.size());

                // Verify the damage rect for the root render pass.
                const RenderPass* root_render_pass = frame.render_passes.back().get();
                EXPECT_EQ(expected_damage, root_render_pass->damage_rect);

                // Verify the root and child layers' quads are generated and not being
                // culled.
                ASSERT_EQ(2u, root_render_pass->quad_list.size());

                LayerImpl* child = host_impl_->active_tree()
                                       ->root_layer_for_testing()
                                       ->test_properties()
                                       ->children[0];
                gfx::Rect expected_child_visible_rect(child->bounds());
                EXPECT_EQ(expected_child_visible_rect,
                    root_render_pass->quad_list.front()->visible_rect);

                LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
                gfx::Rect expected_root_visible_rect(root->bounds());
                EXPECT_EQ(expected_root_visible_rect,
                    root_render_pass->quad_list.ElementAt(1)->visible_rect);
            }

            EXPECT_EQ(expect_to_draw, host_impl_->DrawLayers(&frame));
            host_impl_->DidDrawAllLayers(frame);
        }
    };

    TEST_F(LayerTreeHostImplTestDrawAndTestDamage, FrameIncludesDamageRect)
    {
        std::unique_ptr<SolidColorLayerImpl> root = SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
        root->SetPosition(gfx::PointF());
        root->SetBounds(gfx::Size(10, 10));
        root->SetDrawsContent(true);
        root->test_properties()->force_render_surface = true;

        // Child layer is in the bottom right corner.
        std::unique_ptr<SolidColorLayerImpl> child = SolidColorLayerImpl::Create(host_impl_->active_tree(), 2);
        child->SetPosition(gfx::PointF(9.f, 9.f));
        child->SetBounds(gfx::Size(1, 1));
        child->SetDrawsContent(true);
        root->test_properties()->AddChild(std::move(child));

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        // Draw a frame. In the first frame, the entire viewport should be damaged.
        gfx::Rect full_frame_damage(host_impl_->DrawViewportSize());
        DrawFrameAndTestDamage(full_frame_damage);

        // The second frame has damage that doesn't touch the child layer. Its quads
        // should still be generated.
        gfx::Rect small_damage = gfx::Rect(0, 0, 1, 1);
        host_impl_->active_tree()->root_layer_for_testing()->SetUpdateRect(
            small_damage);
        DrawFrameAndTestDamage(small_damage);

        // The third frame should have no damage, so no quads should be generated.
        gfx::Rect no_damage;
        DrawFrameAndTestDamage(no_damage);
    }

    class GLRendererWithSetupQuadForAntialiasing : public GLRenderer {
    public:
        using GLRenderer::ShouldAntialiasQuad;
    };

    TEST_F(LayerTreeHostImplTest, FarAwayQuadsDontNeedAA)
    {
        // Due to precision issues (especially on Android), sometimes far
        // away quads can end up thinking they need AA.
        float device_scale_factor = 4.f / 3.f;
        gfx::Size root_size(2000, 1000);
        gfx::Size device_viewport_size = gfx::ScaleToCeiledSize(root_size, device_scale_factor);
        host_impl_->SetViewportSize(device_viewport_size);

        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->SetDeviceScaleFactor(device_scale_factor);
        host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f / 16.f,
            16.f);

        std::unique_ptr<LayerImpl> scoped_root = LayerImpl::Create(host_impl_->pending_tree(), 1);
        LayerImpl* root = scoped_root.get();
        root->test_properties()->force_render_surface = true;

        host_impl_->pending_tree()->SetRootLayerForTesting(std::move(scoped_root));

        std::unique_ptr<LayerImpl> scoped_scrolling_layer = LayerImpl::Create(host_impl_->pending_tree(), 2);
        LayerImpl* scrolling_layer = scoped_scrolling_layer.get();
        root->test_properties()->AddChild(std::move(scoped_scrolling_layer));

        gfx::Size content_layer_bounds(100000, 100);
        scoped_refptr<FakeRasterSource> raster_source(
            FakeRasterSource::CreateFilled(content_layer_bounds));

        std::unique_ptr<FakePictureLayerImpl> scoped_content_layer = FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
            3, raster_source);
        LayerImpl* content_layer = scoped_content_layer.get();
        scrolling_layer->test_properties()->AddChild(std::move(scoped_content_layer));
        content_layer->SetBounds(content_layer_bounds);
        content_layer->SetDrawsContent(true);

        root->SetBounds(root_size);

        gfx::ScrollOffset scroll_offset(100000, 0);
        scrolling_layer->SetScrollClipLayer(root->id());
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();

        scrolling_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(scrolling_layer->id(),
                scroll_offset);
        host_impl_->ActivateSyncTree();

        bool update_lcd_text = false;
        host_impl_->active_tree()->UpdateDrawProperties(update_lcd_text);
        ASSERT_EQ(1u, host_impl_->active_tree()->RenderSurfaceLayerList().size());

        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));

        ASSERT_EQ(1u, frame.render_passes.size());
        ASSERT_LE(1u, frame.render_passes[0]->quad_list.size());
        const DrawQuad* quad = frame.render_passes[0]->quad_list.front();

        bool clipped = false, force_aa = false;
        gfx::QuadF device_layer_quad = MathUtil::MapQuad(
            quad->shared_quad_state->quad_to_target_transform,
            gfx::QuadF(gfx::RectF(quad->shared_quad_state->visible_quad_layer_rect)),
            &clipped);
        EXPECT_FALSE(clipped);
        bool antialiased = GLRendererWithSetupQuadForAntialiasing::ShouldAntialiasQuad(
            device_layer_quad, clipped, force_aa);
        EXPECT_FALSE(antialiased);

        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);
    }

    class CompositorFrameMetadataTest : public LayerTreeHostImplTest {
    public:
        CompositorFrameMetadataTest() = default;

        void DidReceiveCompositorFrameAckOnImplThread() override { acks_received_++; }

        int acks_received_ = 0;
    };

    TEST_F(CompositorFrameMetadataTest, CompositorFrameAckCountsAsSwapComplete)
    {
        SetupRootLayerImpl(FakeLayerWithQuads::Create(host_impl_->active_tree(), 1));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        {
            LayerTreeHostImpl::FrameData frame;
            EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
            host_impl_->DrawLayers(&frame);
            host_impl_->DidDrawAllLayers(frame);
        }
        host_impl_->ReclaimResources(ReturnedResourceArray());
        host_impl_->DidReceiveCompositorFrameAck();
        EXPECT_EQ(acks_received_, 1);
    }

    class CountingSoftwareDevice : public SoftwareOutputDevice {
    public:
        CountingSoftwareDevice()
            : frames_began_(0)
            , frames_ended_(0)
        {
        }

        SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override
        {
            ++frames_began_;
            return SoftwareOutputDevice::BeginPaint(damage_rect);
        }
        void EndPaint() override
        {
            SoftwareOutputDevice::EndPaint();
            ++frames_ended_;
        }

        int frames_began_, frames_ended_;
    };

    TEST_F(LayerTreeHostImplTest,
        ForcedDrawToSoftwareDeviceSkipsUnsupportedLayers)
    {
        set_reduce_memory_result(false);
        EXPECT_TRUE(CreateHostImpl(DefaultSettings(),
            FakeCompositorFrameSink::CreateSoftware()));

        const gfx::Transform external_transform;
        const gfx::Rect external_viewport;
        const bool resourceless_software_draw = true;
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        // SolidColorLayerImpl will be drawn.
        std::unique_ptr<SolidColorLayerImpl> root_layer = SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);

        // VideoLayerImpl will not be drawn.
        FakeVideoFrameProvider provider;
        std::unique_ptr<VideoLayerImpl> video_layer = VideoLayerImpl::Create(
            host_impl_->active_tree(), 2, &provider, media::VIDEO_ROTATION_0);
        video_layer->SetBounds(gfx::Size(10, 10));
        video_layer->SetDrawsContent(true);
        root_layer->test_properties()->AddChild(std::move(video_layer));
        SetupRootLayerImpl(std::move(root_layer));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);

        EXPECT_EQ(1u, last_on_draw_frame_->will_draw_layers.size());
        EXPECT_EQ(host_impl_->active_tree()->root_layer_for_testing(),
            last_on_draw_frame_->will_draw_layers[0]);
    }

    // Checks that we use the memory limits provided.
    TEST_F(LayerTreeHostImplTest, MemoryLimits)
    {
        host_impl_->ReleaseCompositorFrameSink();
        host_impl_ = nullptr;

        const size_t kGpuByteLimit = 1234321;
        const size_t kSoftwareByteLimit = 4321234;
        const size_t kGpuResourceLimit = 2345432;
        const size_t kSoftwareResourceLimit = 5432345;
        const gpu::MemoryAllocation::PriorityCutoff kGpuCutoff = gpu::MemoryAllocation::CUTOFF_ALLOW_EVERYTHING;
        const gpu::MemoryAllocation::PriorityCutoff kSoftwareCutoff = gpu::MemoryAllocation::CUTOFF_ALLOW_NICE_TO_HAVE;

        const TileMemoryLimitPolicy kGpuTileCutoff = ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(kGpuCutoff);
        const TileMemoryLimitPolicy kSoftwareTileCutoff = ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(
            kSoftwareCutoff);
        const TileMemoryLimitPolicy kNothingTileCutoff = ManagedMemoryPolicy::PriorityCutoffToTileMemoryLimitPolicy(
            gpu::MemoryAllocation::CUTOFF_ALLOW_NOTHING);
        EXPECT_NE(kGpuTileCutoff, kNothingTileCutoff);
        EXPECT_NE(kSoftwareTileCutoff, kNothingTileCutoff);

        LayerTreeSettings settings = DefaultSettings();
        settings.gpu_memory_policy = ManagedMemoryPolicy(kGpuByteLimit, kGpuCutoff, kGpuResourceLimit);
        settings.software_memory_policy = ManagedMemoryPolicy(
            kSoftwareByteLimit, kSoftwareCutoff, kSoftwareResourceLimit);
        host_impl_ = LayerTreeHostImpl::Create(
            settings, this, &task_runner_provider_, &stats_instrumentation_,
            &task_graph_runner_,
            AnimationHost::CreateForTesting(ThreadInstance::IMPL), 0, nullptr);

        // Gpu compositing.
        compositor_frame_sink_ = FakeCompositorFrameSink::Create3d(TestWebGraphicsContext3D::Create());
        host_impl_->SetVisible(true);
        host_impl_->InitializeRenderer(compositor_frame_sink_.get());
        {
            const auto& state = host_impl_->global_tile_state();
            EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes);
            EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
            EXPECT_EQ(kGpuTileCutoff, state.memory_limit_policy);
        }

        // Not visible, drops to 0.
        host_impl_->SetVisible(false);
        {
            const auto& state = host_impl_->global_tile_state();
            EXPECT_EQ(0u, state.hard_memory_limit_in_bytes);
            EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
            EXPECT_EQ(kNothingTileCutoff, state.memory_limit_policy);
        }

        // Visible, is the gpu limit again.
        host_impl_->SetVisible(true);
        {
            const auto& state = host_impl_->global_tile_state();
            EXPECT_EQ(kGpuByteLimit, state.hard_memory_limit_in_bytes);
            EXPECT_EQ(kGpuResourceLimit, state.num_resources_limit);
        }

        // Software compositing.
        host_impl_->ReleaseCompositorFrameSink();
        compositor_frame_sink_ = FakeCompositorFrameSink::CreateSoftware();
        host_impl_->InitializeRenderer(compositor_frame_sink_.get());
        {
            const auto& state = host_impl_->global_tile_state();
            EXPECT_EQ(kSoftwareByteLimit, state.hard_memory_limit_in_bytes);
            EXPECT_EQ(kSoftwareResourceLimit, state.num_resources_limit);
            EXPECT_EQ(kSoftwareTileCutoff, state.memory_limit_policy);
        }

        // Not visible, drops to 0.
        host_impl_->SetVisible(false);
        {
            const auto& state = host_impl_->global_tile_state();
            EXPECT_EQ(0u, state.hard_memory_limit_in_bytes);
            EXPECT_EQ(kSoftwareResourceLimit, state.num_resources_limit);
            EXPECT_EQ(kNothingTileCutoff, state.memory_limit_policy);
        }

        // Visible, is the software limit again.
        host_impl_->SetVisible(true);
        {
            const auto& state = host_impl_->global_tile_state();
            EXPECT_EQ(kSoftwareByteLimit, state.hard_memory_limit_in_bytes);
            EXPECT_EQ(kSoftwareResourceLimit, state.num_resources_limit);
            EXPECT_EQ(kSoftwareTileCutoff, state.memory_limit_policy);
        }
    }

    TEST_F(LayerTreeHostImplTest, RequireHighResWhenVisible)
    {
        ASSERT_TRUE(host_impl_->active_tree());

        // RequiresHighResToDraw is set when new output surface is used.
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());

        host_impl_->ResetRequiresHighResToDraw();

        host_impl_->SetVisible(false);
        EXPECT_FALSE(host_impl_->RequiresHighResToDraw());
        host_impl_->SetVisible(true);
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());
        host_impl_->SetVisible(false);
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());

        host_impl_->ResetRequiresHighResToDraw();

        EXPECT_FALSE(host_impl_->RequiresHighResToDraw());
        host_impl_->SetVisible(true);
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());
    }

    TEST_F(LayerTreeHostImplTest, RequireHighResAfterGpuRasterizationToggles)
    {
        ASSERT_TRUE(host_impl_->active_tree());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());

        // RequiresHighResToDraw is set when new output surface is used.
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());

        host_impl_->ResetRequiresHighResToDraw();

        host_impl_->SetContentIsSuitableForGpuRasterization(true);
        host_impl_->SetHasGpuRasterizationTrigger(false);
        host_impl_->CommitComplete();
        EXPECT_FALSE(host_impl_->RequiresHighResToDraw());
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->CommitComplete();
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());
        host_impl_->SetHasGpuRasterizationTrigger(false);
        host_impl_->CommitComplete();
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());

        host_impl_->ResetRequiresHighResToDraw();

        EXPECT_FALSE(host_impl_->RequiresHighResToDraw());
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->CommitComplete();
        EXPECT_TRUE(host_impl_->RequiresHighResToDraw());
    }

    class LayerTreeHostImplTestPrepareTiles : public LayerTreeHostImplTest {
    public:
        void SetUp() override
        {
            fake_host_impl_ = new FakeLayerTreeHostImpl(
                LayerTreeSettings(), &task_runner_provider_, &task_graph_runner_);
            host_impl_.reset(fake_host_impl_);
            compositor_frame_sink_ = CreateCompositorFrameSink();
            host_impl_->SetVisible(true);
            host_impl_->InitializeRenderer(compositor_frame_sink_.get());
            host_impl_->SetViewportSize(gfx::Size(10, 10));
        }

        FakeLayerTreeHostImpl* fake_host_impl_;
    };

    TEST_F(LayerTreeHostImplTestPrepareTiles, PrepareTilesWhenInvisible)
    {
        EXPECT_TRUE(fake_host_impl_->prepare_tiles_needed());
        host_impl_->SetVisible(false);
        EXPECT_FALSE(fake_host_impl_->prepare_tiles_needed());
        host_impl_->SetVisible(true);
        EXPECT_TRUE(fake_host_impl_->prepare_tiles_needed());
    }

    TEST_F(LayerTreeHostImplTest, UIResourceManagement)
    {
        std::unique_ptr<TestWebGraphicsContext3D> context = TestWebGraphicsContext3D::Create();
        TestWebGraphicsContext3D* context3d = context.get();
        std::unique_ptr<FakeCompositorFrameSink> compositor_frame_sink = FakeCompositorFrameSink::Create3d();
        CreateHostImpl(DefaultSettings(), std::move(compositor_frame_sink));

        EXPECT_EQ(0u, context3d->NumTextures());

        UIResourceId ui_resource_id = 1;
        bool is_opaque = false;
        UIResourceBitmap bitmap(gfx::Size(1, 1), is_opaque);
        host_impl_->CreateUIResource(ui_resource_id, bitmap);
        EXPECT_EQ(1u, context3d->NumTextures());
        ResourceId id1 = host_impl_->ResourceIdForUIResource(ui_resource_id);
        EXPECT_NE(0u, id1);

        // Multiple requests with the same id is allowed.  The previous texture is
        // deleted.
        host_impl_->CreateUIResource(ui_resource_id, bitmap);
        EXPECT_EQ(1u, context3d->NumTextures());
        ResourceId id2 = host_impl_->ResourceIdForUIResource(ui_resource_id);
        EXPECT_NE(0u, id2);
        EXPECT_NE(id1, id2);

        // Deleting invalid UIResourceId is allowed and does not change state.
        host_impl_->DeleteUIResource(-1);
        EXPECT_EQ(1u, context3d->NumTextures());

        // Should return zero for invalid UIResourceId.  Number of textures should
        // not change.
        EXPECT_EQ(0u, host_impl_->ResourceIdForUIResource(-1));
        EXPECT_EQ(1u, context3d->NumTextures());

        host_impl_->DeleteUIResource(ui_resource_id);
        EXPECT_EQ(0u, host_impl_->ResourceIdForUIResource(ui_resource_id));
        EXPECT_EQ(0u, context3d->NumTextures());

        // Should not change state for multiple deletion on one UIResourceId
        host_impl_->DeleteUIResource(ui_resource_id);
        EXPECT_EQ(0u, context3d->NumTextures());
    }

    TEST_F(LayerTreeHostImplTest, CreateETC1UIResource)
    {
        std::unique_ptr<TestWebGraphicsContext3D> context = TestWebGraphicsContext3D::Create();
        TestWebGraphicsContext3D* context3d = context.get();
        CreateHostImpl(DefaultSettings(), FakeCompositorFrameSink::Create3d());

        EXPECT_EQ(0u, context3d->NumTextures());

        gfx::Size size(4, 4);
        // SkImageInfo has no support for ETC1.  The |info| below contains the right
        // total pixel size for the bitmap but not the right height and width.  The
        // correct width/height are passed directly to UIResourceBitmap.
        SkImageInfo info = SkImageInfo::Make(4, 2, kAlpha_8_SkColorType, kPremul_SkAlphaType);
        sk_sp<SkPixelRef> pixel_ref(SkMallocPixelRef::NewAllocate(info, 0, 0));
        pixel_ref->setImmutable();
        UIResourceBitmap bitmap(std::move(pixel_ref), size);
        UIResourceId ui_resource_id = 1;
        host_impl_->CreateUIResource(ui_resource_id, bitmap);
        EXPECT_EQ(1u, context3d->NumTextures());
        ResourceId id1 = host_impl_->ResourceIdForUIResource(ui_resource_id);
        EXPECT_NE(0u, id1);
    }

    void ShutdownReleasesContext_Callback(
        std::unique_ptr<CopyOutputResult> result) { }

    class FrameSinkClient : public TestCompositorFrameSinkClient {
    public:
        explicit FrameSinkClient(
            scoped_refptr<ContextProvider> display_context_provider)
            : display_context_provider_(std::move(display_context_provider))
        {
        }

        std::unique_ptr<OutputSurface> CreateDisplayOutputSurface(
            scoped_refptr<ContextProvider> compositor_context_provider) override
        {
            return FakeOutputSurface::Create3d(std::move(display_context_provider_));
        }

        void DisplayReceivedCompositorFrame(const CompositorFrame& frame) override { }
        void DisplayWillDrawAndSwap(bool will_draw_and_swap,
            const RenderPassList& render_passes) override { }
        void DisplayDidDrawAndSwap() override { }

    private:
        scoped_refptr<ContextProvider> display_context_provider_;
    };

    TEST_F(LayerTreeHostImplTest, ShutdownReleasesContext)
    {
        scoped_refptr<TestContextProvider> context_provider = TestContextProvider::Create();
        FrameSinkClient test_client_(context_provider);

        auto compositor_frame_sink = base::MakeUnique<TestCompositorFrameSink>(
            context_provider, TestContextProvider::CreateWorker(), nullptr, nullptr,
            RendererSettings(), base::ThreadTaskRunnerHandle::Get().get(),
            true /* synchronous_composite */,
            false /* force_disable_reclaim_resources */);
        compositor_frame_sink->SetClient(&test_client_);

        CreateHostImpl(DefaultSettings(), std::move(compositor_frame_sink));

        SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));

        LayerImpl* root = host_impl_->active_tree()->root_layer_for_testing();
        root->test_properties()->copy_requests.push_back(
            CopyOutputRequest::CreateRequest(
                base::Bind(&ShutdownReleasesContext_Callback)));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        host_impl_->DrawLayers(&frame);
        host_impl_->DidDrawAllLayers(frame);

        // The CopyOutputResult's callback has a ref on the ContextProvider and a
        // texture in a texture mailbox.
        EXPECT_FALSE(context_provider->HasOneRef());
        EXPECT_EQ(1u, context_provider->TestContext3d()->NumTextures());

        host_impl_->ReleaseCompositorFrameSink();
        host_impl_ = nullptr;

        // The CopyOutputResult's callback was cancelled, the CopyOutputResult
        // released, and the texture deleted.
        EXPECT_TRUE(context_provider->HasOneRef());
        EXPECT_EQ(0u, context_provider->TestContext3d()->NumTextures());
    }

    TEST_F(LayerTreeHostImplTest, TouchFlingShouldNotBubble)
    {
        // When flinging via touch, only the child should scroll (we should not
        // bubble).
        gfx::Size surface_size(10, 10);
        gfx::Size content_size(20, 20);
        std::unique_ptr<LayerImpl> root_ptr = LayerImpl::Create(host_impl_->active_tree(), 4);
        std::unique_ptr<LayerImpl> root_clip = LayerImpl::Create(host_impl_->active_tree(), 3);
        root_clip->test_properties()->force_render_surface = true;

        std::unique_ptr<LayerImpl> root_scroll = CreateScrollableLayer(1, content_size, root_clip.get());
        root_scroll->test_properties()->is_container_for_fixed_position_layers = true;
        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(2, content_size, root_clip.get());

        root_scroll->test_properties()->AddChild(std::move(child));
        int root_id = root_scroll->id();
        root_clip->test_properties()->AddChild(std::move(root_scroll));
        root_ptr->test_properties()->AddChild(std::move(root_clip));

        host_impl_->SetViewportSize(surface_size);
        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root_ptr));
        host_impl_->active_tree()->SetViewportLayersFromIds(Layer::INVALID_ID, 4, 1,
            Layer::INVALID_ID);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();
        DrawFrame();
        {
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);

            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);

            gfx::Vector2d scroll_delta(0, 100);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();

            // Only the child should have scrolled.
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            ExpectNone(*scroll_info.get(), root_id);
        }
    }

    TEST_F(LayerTreeHostImplTest, TouchFlingShouldContinueScrollingCurrentLayer)
    {
        // Scroll a child layer beyond its maximum scroll range and make sure the
        // the scroll doesn't bubble up to the parent layer.
        gfx::Size surface_size(10, 10);
        LayerImpl* root = CreateBasicVirtualViewportLayers(surface_size, surface_size);
        root->test_properties()->force_render_surface = true;

        std::unique_ptr<LayerImpl> root_scrolling_owned = CreateScrollableLayer(12, surface_size, root);
        auto* root_scrolling = root_scrolling_owned.get();
        root->test_properties()->AddChild(std::move(root_scrolling_owned));

        std::unique_ptr<LayerImpl> child_owned = CreateScrollableLayer(13, surface_size, root);
        auto* child = child_owned.get();
        root_scrolling->test_properties()->AddChild(std::move(child_owned));

        std::unique_ptr<LayerImpl> grand_child_owned = CreateScrollableLayer(14, surface_size, root);
        auto* grand_child = grand_child_owned.get();
        child->test_properties()->AddChild(std::move(grand_child_owned));

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        child->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(child->id(),
                gfx::ScrollOffset(0, 4));
        grand_child->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(grand_child->id(),
                gfx::ScrollOffset(0, 2));

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();
        {
            std::unique_ptr<ScrollAndScaleSet> scroll_info;

            gfx::Vector2d scroll_delta(0, -2);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_TRUE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);

            // The grand child should have scrolled up to its limit.
            scroll_info = host_impl_->ProcessScrollDeltas();
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            EXPECT_TRUE(
                ScrollInfoContains(*scroll_info, grand_child->id(), scroll_delta));
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);

            // The locked scrolling layer should remain set as the grand child.
            EXPECT_FALSE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);
            scroll_info = host_impl_->ProcessScrollDeltas();
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            EXPECT_TRUE(
                ScrollInfoContains(*scroll_info, grand_child->id(), scroll_delta));
            ExpectNone(*scroll_info, child->id());
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);

            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_FALSE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);

            // The child should not have scrolled.
            scroll_info = host_impl_->ProcessScrollDeltas();
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            EXPECT_TRUE(
                ScrollInfoContains(*scroll_info, grand_child->id(), scroll_delta));
            ExpectNone(*scroll_info, child->id());

            // As the locked layer is at it's limit, no further scrolling can occur.
            EXPECT_FALSE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), grand_child);
            host_impl_->ScrollEnd(EndState().get());
        }
    }

    TEST_F(LayerTreeHostImplTest, WheelFlingShouldntBubble)
    {
        // When flinging via wheel, we shouldn't bubble.
        gfx::Size surface_size(10, 10);
        gfx::Size content_size(20, 20);
        LayerImpl* root_clip = CreateBasicVirtualViewportLayers(surface_size, surface_size);
        root_clip->test_properties()->force_render_surface = true;
        std::unique_ptr<LayerImpl> root_scroll = CreateScrollableLayer(11, content_size, root_clip);
        int root_scroll_id = root_scroll->id();
        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(12, content_size, root_clip);

        root_scroll->test_properties()->AddChild(std::move(child));
        root_clip->test_properties()->AddChild(std::move(root_scroll));

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        host_impl_->SetViewportSize(surface_size);
        DrawFrame();
        {
            EXPECT_EQ(
                InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                    .thread);

            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);

            gfx::Vector2d scroll_delta(0, 100);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());

            host_impl_->ScrollEnd(EndState().get());

            std::unique_ptr<ScrollAndScaleSet> scroll_info = host_impl_->ProcessScrollDeltas();

            // The root shouldn't have scrolled.
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            ExpectNone(*scroll_info.get(), root_scroll_id);
        }
    }

    TEST_F(LayerTreeHostImplTest, ScrollUnknownNotOnAncestorChain)
    {
        // If we ray cast a scroller that is not on the first layer's ancestor chain,
        // we should return SCROLL_UNKNOWN.
        gfx::Size content_size(100, 100);
        SetupScrollAndContentsLayers(content_size);

        int scroll_layer_id = 2;
        LayerImpl* scroll_layer = host_impl_->active_tree()->LayerById(scroll_layer_id);
        scroll_layer->SetDrawsContent(true);

        int page_scale_layer_id = 5;
        LayerImpl* page_scale_layer = host_impl_->active_tree()->LayerById(page_scale_layer_id);

        int occluder_layer_id = 6;
        std::unique_ptr<LayerImpl> occluder_layer = LayerImpl::Create(host_impl_->active_tree(), occluder_layer_id);
        occluder_layer->SetDrawsContent(true);
        occluder_layer->SetBounds(content_size);
        occluder_layer->SetPosition(gfx::PointF());

        // The parent of the occluder is *above* the scroller.
        page_scale_layer->test_properties()->AddChild(std::move(occluder_layer));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ScrollUnknownScrollAncestorMismatch)
    {
        // If we ray cast a scroller this is on the first layer's ancestor chain, but
        // is not the first scroller we encounter when walking up from the layer, we
        // should also return SCROLL_UNKNOWN.
        gfx::Size content_size(100, 100);
        SetupScrollAndContentsLayers(content_size);

        int scroll_layer_id = 2;
        LayerImpl* scroll_layer = host_impl_->active_tree()->LayerById(scroll_layer_id);
        scroll_layer->SetDrawsContent(true);

        int occluder_layer_id = 6;
        std::unique_ptr<LayerImpl> occluder_layer = LayerImpl::Create(host_impl_->active_tree(), occluder_layer_id);
        occluder_layer->SetDrawsContent(true);
        occluder_layer->SetBounds(content_size);
        occluder_layer->SetPosition(gfx::PointF(-10.f, -10.f));

        int child_scroll_clip_layer_id = 7;
        std::unique_ptr<LayerImpl> child_scroll_clip = LayerImpl::Create(host_impl_->active_tree(), child_scroll_clip_layer_id);

        int child_scroll_layer_id = 8;
        std::unique_ptr<LayerImpl> child_scroll = CreateScrollableLayer(
            child_scroll_layer_id, content_size, child_scroll_clip.get());

        child_scroll->SetPosition(gfx::PointF(10.f, 10.f));

        child_scroll->test_properties()->AddChild(std::move(occluder_layer));
        child_scroll_clip->test_properties()->AddChild(std::move(child_scroll));
        scroll_layer->test_properties()->AddChild(std::move(child_scroll_clip));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        InputHandler::ScrollStatus status = host_impl_->ScrollBegin(
            BeginState(gfx::Point()).get(), InputHandler::WHEEL);
        EXPECT_EQ(InputHandler::SCROLL_UNKNOWN, status.thread);
        EXPECT_EQ(MainThreadScrollingReason::kFailedHitTest,
            status.main_thread_scrolling_reasons);
    }

    TEST_F(LayerTreeHostImplTest, ScrollInvisibleScroller)
    {
        gfx::Size content_size(100, 100);
        SetupScrollAndContentsLayers(content_size);

        LayerImpl* root = host_impl_->active_tree()->LayerById(1);

        int scroll_layer_id = 2;
        LayerImpl* scroll_layer = host_impl_->active_tree()->LayerById(scroll_layer_id);

        int child_scroll_layer_id = 7;
        std::unique_ptr<LayerImpl> child_scroll = CreateScrollableLayer(child_scroll_layer_id, content_size, root);
        child_scroll->SetDrawsContent(false);

        scroll_layer->test_properties()->AddChild(std::move(child_scroll));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        // We should have scrolled |child_scroll| even though it does not move
        // any layer that is a drawn RSLL member.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);

        EXPECT_EQ(7, host_impl_->CurrentlyScrollingLayer()->id());
    }

    // Make sure LatencyInfo carried by LatencyInfoSwapPromise are passed
    // in CompositorFrameMetadata.
    TEST_F(LayerTreeHostImplTest, LatencyInfoPassedToCompositorFrameMetadata)
    {
        std::unique_ptr<SolidColorLayerImpl> root = SolidColorLayerImpl::Create(host_impl_->active_tree(), 1);
        root->SetPosition(gfx::PointF());
        root->SetBounds(gfx::Size(10, 10));
        root->SetDrawsContent(true);
        root->test_properties()->force_render_surface = true;

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        FakeCompositorFrameSink* fake_compositor_frame_sink = static_cast<FakeCompositorFrameSink*>(
            host_impl_->compositor_frame_sink());

        ui::LatencyInfo latency_info;
        latency_info.AddLatencyNumber(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0,
            0);
        std::unique_ptr<SwapPromise> swap_promise(
            new LatencyInfoSwapPromise(latency_info));
        host_impl_->active_tree()->QueuePinnedSwapPromise(std::move(swap_promise));

        gfx::Rect full_frame_damage(host_impl_->DrawViewportSize());
        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        EXPECT_TRUE(host_impl_->DrawLayers(&frame));
        host_impl_->DidDrawAllLayers(frame);

        const std::vector<ui::LatencyInfo>& metadata_latency_after = fake_compositor_frame_sink->last_sent_frame()->metadata.latency_info;
        EXPECT_EQ(1u, metadata_latency_after.size());
        EXPECT_TRUE(metadata_latency_after[0].FindLatency(
            ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, 0, NULL));
    }

    TEST_F(LayerTreeHostImplTest, SelectionBoundsPassedToCompositorFrameMetadata)
    {
        int root_layer_id = 1;
        std::unique_ptr<SolidColorLayerImpl> root = SolidColorLayerImpl::Create(host_impl_->active_tree(), root_layer_id);
        root->SetPosition(gfx::PointF());
        root->SetBounds(gfx::Size(10, 10));
        root->SetDrawsContent(true);
        root->test_properties()->force_render_surface = true;

        host_impl_->active_tree()->SetRootLayerForTesting(std::move(root));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        // Ensure the default frame selection bounds are empty.
        FakeCompositorFrameSink* fake_compositor_frame_sink = static_cast<FakeCompositorFrameSink*>(
            host_impl_->compositor_frame_sink());

        // Plumb the layer-local selection bounds.
        gfx::Point selection_top(5, 0);
        gfx::Point selection_bottom(5, 5);
        LayerSelection selection;
        selection.start.type = gfx::SelectionBound::CENTER;
        selection.start.layer_id = root_layer_id;
        selection.start.edge_bottom = selection_bottom;
        selection.start.edge_top = selection_top;
        selection.end = selection.start;
        host_impl_->active_tree()->RegisterSelection(selection);

        // Trigger a draw-swap sequence.
        host_impl_->SetNeedsRedraw();

        gfx::Rect full_frame_damage(host_impl_->DrawViewportSize());
        LayerTreeHostImpl::FrameData frame;
        EXPECT_EQ(DRAW_SUCCESS, host_impl_->PrepareToDraw(&frame));
        EXPECT_TRUE(host_impl_->DrawLayers(&frame));
        host_impl_->DidDrawAllLayers(frame);

        // Ensure the selection bounds have propagated to the frame metadata.
        const Selection<gfx::SelectionBound>& selection_after = fake_compositor_frame_sink->last_sent_frame()->metadata.selection;
        EXPECT_EQ(selection.start.type, selection_after.start.type());
        EXPECT_EQ(selection.end.type, selection_after.end.type());
        EXPECT_EQ(gfx::PointF(selection_bottom), selection_after.start.edge_bottom());
        EXPECT_EQ(gfx::PointF(selection_top), selection_after.start.edge_top());
        EXPECT_TRUE(selection_after.start.visible());
        EXPECT_TRUE(selection_after.start.visible());
    }

    class SimpleSwapPromiseMonitor : public SwapPromiseMonitor {
    public:
        SimpleSwapPromiseMonitor(LayerTreeHost* layer_tree_host,
            LayerTreeHostImpl* layer_tree_host_impl,
            int* set_needs_commit_count,
            int* set_needs_redraw_count,
            int* forward_to_main_count)
            : SwapPromiseMonitor(
                (layer_tree_host ? layer_tree_host->GetSwapPromiseManager()
                                 : nullptr),
                layer_tree_host_impl)
            , set_needs_commit_count_(set_needs_commit_count)
            , set_needs_redraw_count_(set_needs_redraw_count)
            , forward_to_main_count_(forward_to_main_count)
        {
        }

        ~SimpleSwapPromiseMonitor() override { }

        void OnSetNeedsCommitOnMain() override { (*set_needs_commit_count_)++; }

        void OnSetNeedsRedrawOnImpl() override { (*set_needs_redraw_count_)++; }

        void OnForwardScrollUpdateToMainThreadOnImpl() override
        {
            (*forward_to_main_count_)++;
        }

    private:
        int* set_needs_commit_count_;
        int* set_needs_redraw_count_;
        int* forward_to_main_count_;
    };

    TEST_F(LayerTreeHostImplTest, SimpleSwapPromiseMonitor)
    {
        int set_needs_commit_count = 0;
        int set_needs_redraw_count = 0;
        int forward_to_main_count = 0;

        {
            std::unique_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor(
                new SimpleSwapPromiseMonitor(
                    NULL, host_impl_.get(), &set_needs_commit_count,
                    &set_needs_redraw_count, &forward_to_main_count));
            host_impl_->SetNeedsRedraw();
            EXPECT_EQ(0, set_needs_commit_count);
            EXPECT_EQ(1, set_needs_redraw_count);
            EXPECT_EQ(0, forward_to_main_count);
        }

        // Now the monitor is destroyed, SetNeedsRedraw() is no longer being
        // monitored.
        host_impl_->SetNeedsRedraw();
        EXPECT_EQ(0, set_needs_commit_count);
        EXPECT_EQ(1, set_needs_redraw_count);
        EXPECT_EQ(0, forward_to_main_count);

        {
            std::unique_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor(
                new SimpleSwapPromiseMonitor(
                    NULL, host_impl_.get(), &set_needs_commit_count,
                    &set_needs_redraw_count, &forward_to_main_count));
            // Redraw with damage.
            host_impl_->SetFullViewportDamage();
            host_impl_->SetNeedsRedraw();
            EXPECT_EQ(0, set_needs_commit_count);
            EXPECT_EQ(2, set_needs_redraw_count);
            EXPECT_EQ(0, forward_to_main_count);
        }

        {
            std::unique_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor(
                new SimpleSwapPromiseMonitor(
                    NULL, host_impl_.get(), &set_needs_commit_count,
                    &set_needs_redraw_count, &forward_to_main_count));
            // Redraw without damage.
            host_impl_->SetNeedsRedraw();
            EXPECT_EQ(0, set_needs_commit_count);
            EXPECT_EQ(3, set_needs_redraw_count);
            EXPECT_EQ(0, forward_to_main_count);
        }

        set_needs_commit_count = 0;
        set_needs_redraw_count = 0;
        forward_to_main_count = 0;

        {
            std::unique_ptr<SimpleSwapPromiseMonitor> swap_promise_monitor(
                new SimpleSwapPromiseMonitor(
                    NULL, host_impl_.get(), &set_needs_commit_count,
                    &set_needs_redraw_count, &forward_to_main_count));
            SetupScrollAndContentsLayers(gfx::Size(100, 100));

            // Scrolling normally should not trigger any forwarding.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_TRUE(
                host_impl_
                    ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get())
                    .did_scroll);
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_EQ(0, set_needs_commit_count);
            EXPECT_EQ(1, set_needs_redraw_count);
            EXPECT_EQ(0, forward_to_main_count);

            // Scrolling with a scroll handler should defer the swap to the main
            // thread.
            host_impl_->active_tree()->set_have_scroll_event_handlers(true);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_TRUE(
                host_impl_
                    ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, 10)).get())
                    .did_scroll);
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_EQ(0, set_needs_commit_count);
            EXPECT_EQ(2, set_needs_redraw_count);
            EXPECT_EQ(1, forward_to_main_count);
        }
    }

    class LayerTreeHostImplWithBrowserControlsTest : public LayerTreeHostImplTest {
    public:
        void SetUp() override
        {
            LayerTreeSettings settings = DefaultSettings();
            CreateHostImpl(settings, CreateCompositorFrameSink());
            host_impl_->active_tree()->set_top_controls_height(top_controls_height_);
            host_impl_->sync_tree()->set_top_controls_height(top_controls_height_);
            host_impl_->active_tree()->SetCurrentBrowserControlsShownRatio(1.f);
        }

    protected:
        static const int top_controls_height_;
    };

    const int LayerTreeHostImplWithBrowserControlsTest::top_controls_height_ = 50;

    TEST_F(LayerTreeHostImplWithBrowserControlsTest, NoIdleAnimations)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->id(),
                gfx::ScrollOffset(0, 10));
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2);
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        EXPECT_FALSE(did_request_redraw_);
        host_impl_->DidFinishImplFrame();
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsHeightIsCommitted)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        EXPECT_FALSE(did_request_redraw_);
        host_impl_->CreatePendingTree();
        host_impl_->sync_tree()->set_top_controls_height(100);
        host_impl_->ActivateSyncTree();
        EXPECT_EQ(100, host_impl_->browser_controls_manager()->TopControlsHeight());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsStayFullyVisibleOnHeightChange)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 100));
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ControlsTopOffset());

        host_impl_->CreatePendingTree();
        host_impl_->sync_tree()->set_top_controls_height(0);
        host_impl_->ActivateSyncTree();
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ControlsTopOffset());

        host_impl_->CreatePendingTree();
        host_impl_->sync_tree()->set_top_controls_height(50);
        host_impl_->ActivateSyncTree();
        EXPECT_EQ(0.f, host_impl_->browser_controls_manager()->ControlsTopOffset());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationScheduling)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(scroll_layer->id(),
                gfx::ScrollOffset(0, 10));
        host_impl_->DidChangeBrowserControlsPosition();
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_TRUE(did_request_redraw_);
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        ScrollHandledByBrowserControls)
    {
        InputHandlerScrollResult result;
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(100, 100));
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, false);
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Scroll just the browser controls and verify that the scroll succeeds.
        const float residue = 10;
        float offset = top_controls_height_ - residue;
        result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get());
        EXPECT_EQ(result.unused_scroll_delta, gfx::Vector2d(0, 0));
        EXPECT_TRUE(result.did_scroll);
        EXPECT_FLOAT_EQ(-offset,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Scroll across the boundary
        const float content_scroll = 20;
        offset = residue + content_scroll;
        result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get());
        EXPECT_TRUE(result.did_scroll);
        EXPECT_EQ(result.unused_scroll_delta, gfx::Vector2d(0, 0));
        EXPECT_EQ(-top_controls_height_,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF(0, content_scroll).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Now scroll back to the top of the content
        offset = -content_scroll;
        result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get());
        EXPECT_TRUE(result.did_scroll);
        EXPECT_EQ(result.unused_scroll_delta, gfx::Vector2d(0, 0));
        EXPECT_EQ(-top_controls_height_,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // And scroll the browser controls completely into view
        offset = -top_controls_height_;
        result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get());
        EXPECT_TRUE(result.did_scroll);
        EXPECT_EQ(result.unused_scroll_delta, gfx::Vector2d(0, 0));
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // And attempt to scroll past the end
        result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get());
        EXPECT_FALSE(result.did_scroll);
        EXPECT_EQ(result.unused_scroll_delta, gfx::Vector2d(0, -50));
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        host_impl_->ScrollEnd(EndState().get());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        WheelUnhandledByBrowserControls)
    {
        SetupScrollAndContentsLayers(gfx::Size(100, 200));
        host_impl_->SetViewportSize(gfx::Size(50, 100));
        host_impl_->active_tree()->set_browser_controls_shrink_blink_size(true);
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, false);
        DrawFrame();

        LayerImpl* viewport_layer = host_impl_->InnerViewportScrollLayer();

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                .thread);
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), viewport_layer->CurrentScrollOffset());

        // Wheel scrolls should not affect the browser controls, and should pass
        // directly through to the viewport.
        const float delta = top_controls_height_;
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, delta)).get())
                .did_scroll);
        EXPECT_FLOAT_EQ(0,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, delta),
            viewport_layer->CurrentScrollOffset());

        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, delta)).get())
                .did_scroll);
        EXPECT_FLOAT_EQ(0,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, delta * 2),
            viewport_layer->CurrentScrollOffset());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationAtOrigin)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(100, 200));
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, false);
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Scroll the browser controls partially.
        const float residue = 35;
        float offset = top_controls_height_ - residue;
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get())
                .did_scroll);
        EXPECT_FLOAT_EQ(-offset,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        did_request_commit_ = false;

        // End the scroll while the controls are still offset from their limit.
        host_impl_->ScrollEnd(EndState().get());
        ASSERT_TRUE(host_impl_->browser_controls_manager()->has_animation());
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        // The browser controls should properly animate until finished, despite the
        // scroll offset being at the origin.
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(
            BEGINFRAME_FROM_HERE, 0, 1, base::TimeTicks::Now());
        while (did_request_next_frame_) {
            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            did_request_commit_ = false;

            float old_offset = host_impl_->browser_controls_manager()->ControlsTopOffset();

            begin_frame_args.frame_time += base::TimeDelta::FromMilliseconds(5);
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();
            EXPECT_EQ(gfx::Vector2dF().ToString(),
                scroll_layer->CurrentScrollOffset().ToString());

            float new_offset = host_impl_->browser_controls_manager()->ControlsTopOffset();

            // No commit is needed as the controls are animating the content offset,
            // not the scroll offset.
            EXPECT_FALSE(did_request_commit_);

            if (new_offset != old_offset)
                EXPECT_TRUE(did_request_redraw_);

            if (new_offset != 0) {
                EXPECT_TRUE(host_impl_->browser_controls_manager()->has_animation());
                EXPECT_TRUE(did_request_next_frame_);
            }
            host_impl_->DidFinishImplFrame();
        }
        EXPECT_FALSE(host_impl_->browser_controls_manager()->has_animation());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationAfterScroll)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(100, 100));
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, false);
        float initial_scroll_offset = 50;
        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                scroll_layer->id(), gfx::ScrollOffset(0, initial_scroll_offset));
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF(0, initial_scroll_offset).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Scroll the browser controls partially.
        const float residue = 15;
        float offset = top_controls_height_ - residue;
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get())
                .did_scroll);
        EXPECT_FLOAT_EQ(-offset,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF(0, initial_scroll_offset).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        did_request_commit_ = false;

        // End the scroll while the controls are still offset from the limit.
        host_impl_->ScrollEnd(EndState().get());
        ASSERT_TRUE(host_impl_->browser_controls_manager()->has_animation());
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        // Animate the browser controls to the limit.
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(
            BEGINFRAME_FROM_HERE, 0, 1, base::TimeTicks::Now());
        while (did_request_next_frame_) {
            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            did_request_commit_ = false;

            float old_offset = host_impl_->browser_controls_manager()->ControlsTopOffset();

            begin_frame_args.frame_time += base::TimeDelta::FromMilliseconds(5);
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();

            float new_offset = host_impl_->browser_controls_manager()->ControlsTopOffset();

            if (new_offset != old_offset) {
                EXPECT_TRUE(did_request_redraw_);
                EXPECT_TRUE(did_request_commit_);
            }
            host_impl_->DidFinishImplFrame();
        }
        EXPECT_FALSE(host_impl_->browser_controls_manager()->has_animation());
        EXPECT_EQ(-top_controls_height_,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsAnimationAfterMainThreadFlingStopped)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(100, 100));
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, false);
        float initial_scroll_offset = 50;
        scroll_layer->layer_tree_impl()
            ->property_trees()
            ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                scroll_layer->id(), gfx::ScrollOffset(0, initial_scroll_offset));
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF(0, initial_scroll_offset).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Scroll the browser controls partially.
        const float residue = 15;
        float offset = top_controls_height_ - residue;
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get())
                .did_scroll);
        EXPECT_FLOAT_EQ(-offset,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF(0, initial_scroll_offset).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        did_request_redraw_ = false;
        did_request_next_frame_ = false;
        did_request_commit_ = false;

        // End the fling while the controls are still offset from the limit.
        host_impl_->MainThreadHasStoppedFlinging();
        ASSERT_TRUE(host_impl_->browser_controls_manager()->has_animation());
        EXPECT_TRUE(did_request_next_frame_);
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(did_request_commit_);

        // Animate the browser controls to the limit.
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(
            BEGINFRAME_FROM_HERE, 0, 1, base::TimeTicks::Now());
        while (did_request_next_frame_) {
            did_request_redraw_ = false;
            did_request_next_frame_ = false;
            did_request_commit_ = false;

            float old_offset = host_impl_->browser_controls_manager()->ControlsTopOffset();

            begin_frame_args.frame_time += base::TimeDelta::FromMilliseconds(5);
            begin_frame_args.sequence_number++;
            host_impl_->WillBeginImplFrame(begin_frame_args);
            host_impl_->Animate();

            float new_offset = host_impl_->browser_controls_manager()->ControlsTopOffset();

            if (new_offset != old_offset) {
                EXPECT_TRUE(did_request_redraw_);
                EXPECT_TRUE(did_request_commit_);
            }
            host_impl_->DidFinishImplFrame();
        }
        EXPECT_FALSE(host_impl_->browser_controls_manager()->has_animation());
        EXPECT_EQ(-top_controls_height_,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
    }

    TEST_F(LayerTreeHostImplWithBrowserControlsTest,
        BrowserControlsScrollDeltaInOverScroll)
    {
        // Verifies that the overscroll delta should not have accumulated in
        // the browser controls if we do a hide and show without releasing finger.
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 200));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        host_impl_->SetViewportSize(gfx::Size(100, 100));
        host_impl_->browser_controls_manager()->UpdateBrowserControlsState(
            BOTH, SHOWN, false);
        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());

        float offset = 50;
        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get())
                .did_scroll);
        EXPECT_EQ(-offset,
            host_impl_->browser_controls_manager()->ControlsTopOffset());
        EXPECT_EQ(gfx::Vector2dF().ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get())
                .did_scroll);
        EXPECT_EQ(gfx::Vector2dF(0, offset).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, offset)).get())
                .did_scroll);

        // Should have fully scrolled
        EXPECT_EQ(gfx::Vector2dF(0, scroll_layer->MaxScrollOffset().y()).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        float overscrollamount = 10;

        // Overscroll the content
        EXPECT_FALSE(host_impl_
                         ->ScrollBy(UpdateState(gfx::Point(),
                             gfx::Vector2d(0, overscrollamount))
                                        .get())
                         .did_scroll);
        EXPECT_EQ(gfx::Vector2dF(0, 2 * offset).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());
        EXPECT_EQ(gfx::Vector2dF(0, overscrollamount).ToString(),
            host_impl_->accumulated_root_overscroll().ToString());

        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(
                    UpdateState(gfx::Point(), gfx::Vector2d(0, -2 * offset)).get())
                .did_scroll);
        EXPECT_EQ(gfx::Vector2dF(0, 0).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());
        EXPECT_EQ(-offset,
            host_impl_->browser_controls_manager()->ControlsTopOffset());

        EXPECT_TRUE(
            host_impl_
                ->ScrollBy(UpdateState(gfx::Point(), gfx::Vector2d(0, -offset)).get())
                .did_scroll);
        EXPECT_EQ(gfx::Vector2dF(0, 0).ToString(),
            scroll_layer->CurrentScrollOffset().ToString());

        // Browser controls should be fully visible
        EXPECT_EQ(0, host_impl_->browser_controls_manager()->ControlsTopOffset());

        host_impl_->ScrollEnd(EndState().get());
    }

    // Tests that when we set a child scroller (e.g. a scrolling div) as the outer
    // viewport, scrolling it controls the browser controls.
    TEST_F(LayerTreeHostImplBrowserControlsTest,
        ReplacedOuterViewportScrollsBrowserControls)
    {
        const gfx::Size scroll_content_size(400, 400);
        const gfx::Size root_layer_size(200, 200);
        const gfx::Size viewport_size(100, 100);

        SetupBrowserControlsAndScrollLayerWithVirtualViewport(
            viewport_size, viewport_size, root_layer_size);

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
        LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
        LayerImpl* scroll_layer = nullptr;

        // Initialization: Add a child scrolling layer to the outer scroll layer and
        // set its scroll layer as the outer viewport. This simulates setting a
        // scrolling element as the root scroller on the page.
        {
            std::unique_ptr<LayerImpl> clip = LayerImpl::Create(layer_tree_impl, 10);
            clip->SetBounds(root_layer_size);
            clip->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> scroll = LayerImpl::Create(layer_tree_impl, 11);
            scroll->SetBounds(scroll_content_size);
            scroll->SetScrollClipLayer(clip->id());
            scroll->SetDrawsContent(true);

            scroll_layer = scroll.get();

            clip->test_properties()->AddChild(std::move(scroll));
            outer_scroll->test_properties()->AddChild(std::move(clip));
            layer_tree_impl->SetViewportLayersFromIds(
                Layer::INVALID_ID, layer_tree_impl->PageScaleLayer()->id(),
                inner_scroll->id(), scroll_layer->id());
            layer_tree_impl->BuildPropertyTreesForTesting();
            DrawFrame();
        }

        ASSERT_EQ(1.f, host_impl_->active_tree()->CurrentBrowserControlsShownRatio());

        // Scrolling should scroll the child content and the browser controls. The
        // original outer viewport should get no scroll.
        {
            host_impl_->ScrollBegin(BeginState(gfx::Point(0, 0)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(0, 0), gfx::Vector2dF(100.f, 100.f)).get());
            host_impl_->ScrollEnd(EndState().get());

            EXPECT_VECTOR_EQ(gfx::Vector2dF(), outer_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(100.f, 50.f),
                scroll_layer->CurrentScrollOffset());
            EXPECT_EQ(0.f,
                host_impl_->active_tree()->CurrentBrowserControlsShownRatio());
        }
    }

    class LayerTreeHostImplVirtualViewportTest : public LayerTreeHostImplTest {
    public:
        void SetupVirtualViewportLayers(const gfx::Size& content_size,
            const gfx::Size& outer_viewport,
            const gfx::Size& inner_viewport)
        {
            LayerTreeImpl* layer_tree_impl = host_impl_->active_tree();
            const int kOuterViewportClipLayerId = 6;
            const int kOuterViewportScrollLayerId = 7;
            const int kInnerViewportScrollLayerId = 2;
            const int kInnerViewportClipLayerId = 4;
            const int kPageScaleLayerId = 5;

            std::unique_ptr<LayerImpl> inner_scroll = LayerImpl::Create(layer_tree_impl, kInnerViewportScrollLayerId);
            inner_scroll->test_properties()->is_container_for_fixed_position_layers = true;
            inner_scroll->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(inner_scroll->id(),
                    gfx::ScrollOffset());

            std::unique_ptr<LayerImpl> inner_clip = LayerImpl::Create(layer_tree_impl, kInnerViewportClipLayerId);
            inner_clip->SetBounds(inner_viewport);

            std::unique_ptr<LayerImpl> page_scale = LayerImpl::Create(layer_tree_impl, kPageScaleLayerId);

            inner_scroll->SetScrollClipLayer(inner_clip->id());
            inner_scroll->SetBounds(outer_viewport);
            inner_scroll->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> outer_clip = LayerImpl::Create(layer_tree_impl, kOuterViewportClipLayerId);
            outer_clip->SetBounds(outer_viewport);
            outer_clip->test_properties()->is_container_for_fixed_position_layers = true;

            std::unique_ptr<LayerImpl> outer_scroll = LayerImpl::Create(layer_tree_impl, kOuterViewportScrollLayerId);
            outer_scroll->SetScrollClipLayer(outer_clip->id());
            outer_scroll->layer_tree_impl()
                ->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(outer_scroll->id(),
                    gfx::ScrollOffset());
            outer_scroll->SetBounds(content_size);
            outer_scroll->SetPosition(gfx::PointF());

            std::unique_ptr<LayerImpl> contents = LayerImpl::Create(layer_tree_impl, 8);
            contents->SetDrawsContent(true);
            contents->SetBounds(content_size);
            contents->SetPosition(gfx::PointF());

            outer_scroll->test_properties()->AddChild(std::move(contents));
            outer_clip->test_properties()->AddChild(std::move(outer_scroll));
            inner_scroll->test_properties()->AddChild(std::move(outer_clip));
            page_scale->test_properties()->AddChild(std::move(inner_scroll));
            inner_clip->test_properties()->AddChild(std::move(page_scale));

            inner_clip->test_properties()->force_render_surface = true;
            layer_tree_impl->SetRootLayerForTesting(std::move(inner_clip));
            layer_tree_impl->SetViewportLayersFromIds(
                Layer::INVALID_ID, kPageScaleLayerId, kInnerViewportScrollLayerId,
                kOuterViewportScrollLayerId);

            host_impl_->active_tree()->BuildPropertyTreesForTesting();
            host_impl_->active_tree()->DidBecomeActive();
        }
    };

    TEST_F(LayerTreeHostImplVirtualViewportTest, ScrollBothInnerAndOuterLayer)
    {
        gfx::Size content_size = gfx::Size(100, 160);
        gfx::Size outer_viewport = gfx::Size(50, 80);
        gfx::Size inner_viewport = gfx::Size(25, 40);

        SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();

        DrawFrame();
        {
            gfx::ScrollOffset inner_expected;
            gfx::ScrollOffset outer_expected;
            EXPECT_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_EQ(outer_expected, outer_scroll->CurrentScrollOffset());

            gfx::ScrollOffset current_offset(70.f, 100.f);

            host_impl_->SetSynchronousInputHandlerRootScrollOffset(current_offset);
            EXPECT_EQ(gfx::ScrollOffset(25.f, 40.f), inner_scroll->MaxScrollOffset());
            EXPECT_EQ(gfx::ScrollOffset(50.f, 80.f), outer_scroll->MaxScrollOffset());

            // Outer viewport scrolls first. Then the rest is applied to the inner
            // viewport.
            EXPECT_EQ(gfx::ScrollOffset(20.f, 20.f),
                inner_scroll->CurrentScrollOffset());
            EXPECT_EQ(gfx::ScrollOffset(50.f, 80.f),
                outer_scroll->CurrentScrollOffset());
        }
    }

    TEST_F(LayerTreeHostImplVirtualViewportTest, FlingScrollBubblesToInner)
    {
        gfx::Size content_size = gfx::Size(200, 320);
        gfx::Size outer_viewport = gfx::Size(100, 160);
        gfx::Size inner_viewport = gfx::Size(50, 80);

        SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();

        DrawFrame();
        {
            gfx::Vector2dF inner_expected;
            gfx::Vector2dF outer_expected;
            EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset());

            // Scrolling the viewport always sets the outer scroll layer as the
            // currently scrolling layer.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());

            gfx::Vector2d scroll_delta(inner_viewport.width() / 2.f,
                inner_viewport.height() / 2.f);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            inner_expected += gfx::Vector2dF(scroll_delta.x(), scroll_delta.y());
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());

            host_impl_->ScrollEnd(EndState().get());
            EXPECT_EQ(nullptr, host_impl_->CurrentlyScrollingLayer());

            EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset());

            // Fling past the inner viewport boundry, make sure outer viewport scrolls.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());

            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            inner_expected += gfx::Vector2dF(scroll_delta.x(), scroll_delta.y());
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());

            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            outer_expected += gfx::Vector2dF(scroll_delta.x(), scroll_delta.y());
            EXPECT_EQ(outer_scroll, host_impl_->CurrentlyScrollingLayer());

            host_impl_->ScrollEnd(EndState().get());
            EXPECT_EQ(nullptr, host_impl_->CurrentlyScrollingLayer());

            EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset());
        }
    }

    TEST_F(LayerTreeHostImplVirtualViewportTest,
        DiagonalScrollBubblesPerfectlyToInner)
    {
        gfx::Size content_size = gfx::Size(200, 320);
        gfx::Size outer_viewport = gfx::Size(100, 160);
        gfx::Size inner_viewport = gfx::Size(50, 80);

        SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();

        DrawFrame();
        {
            gfx::Vector2dF inner_expected;
            gfx::Vector2dF outer_expected;
            EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset());

            // Make sure the scroll goes to the inner viewport first.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));

            // Scroll near the edge of the outer viewport.
            gfx::Vector2d scroll_delta(inner_viewport.width() / 2.f,
                inner_viewport.height() / 2.f);
            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            inner_expected += scroll_delta;
            EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));

            EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset());

            // Now diagonal scroll across the outer viewport boundary in a single event.
            // The entirety of the scroll should be consumed, as bubbling between inner
            // and outer viewport layers is perfect.
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::ScaleVector2d(scroll_delta, 2)).get());
            EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));
            outer_expected += scroll_delta;
            inner_expected += scroll_delta;
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));

            EXPECT_VECTOR_EQ(inner_expected, inner_scroll->CurrentScrollOffset());
            EXPECT_VECTOR_EQ(outer_expected, outer_scroll->CurrentScrollOffset());
        }
    }

    TEST_F(LayerTreeHostImplVirtualViewportTest,
        TouchFlingDoesntSwitchScrollingLayer)
    {
        gfx::Size content_size = gfx::Size(100, 160);
        gfx::Size outer_viewport = gfx::Size(50, 80);
        gfx::Size inner_viewport = gfx::Size(25, 40);

        SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();

        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(10, outer_viewport, outer_scroll);
        LayerImpl* child_scroll = child.get();
        outer_scroll->test_properties()->children[0]->test_properties()->AddChild(
            std::move(child));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();
        {
            std::unique_ptr<ScrollAndScaleSet> scroll_info;

            gfx::Vector2d scroll_delta(0, inner_viewport.height());
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_TRUE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);
            EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));

            // The child should have scrolled up to its limit.
            scroll_info = host_impl_->ProcessScrollDeltas();
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            EXPECT_TRUE(
                ScrollInfoContains(*scroll_info, child_scroll->id(), scroll_delta));
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child_scroll);

            // The fling have no effect on the currently scrolling layer.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_FALSE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child_scroll);
            EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));

            // The inner viewport shouldn't have scrolled.
            scroll_info = host_impl_->ProcessScrollDeltas();
            ASSERT_EQ(1u, scroll_info->scrolls.size());
            EXPECT_TRUE(
                ScrollInfoContains(*scroll_info, child_scroll->id(), scroll_delta));
            ExpectNone(*scroll_info, inner_scroll->id());

            // As the locked layer is at its limit, no further scrolling can occur.
            EXPECT_FALSE(
                host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get())
                    .did_scroll);
            EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child_scroll);
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(
                gfx::Point(), InputHandler::TOUCHSCREEN));
        }
    }

    TEST_F(LayerTreeHostImplVirtualViewportTest,
        ScrollBeginEventThatTargetsViewportLayerSkipsHitTest)
    {
        gfx::Size content_size = gfx::Size(100, 160);
        gfx::Size outer_viewport = gfx::Size(50, 80);
        gfx::Size inner_viewport = gfx::Size(25, 40);

        SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);

        LayerImpl* outer_scroll = host_impl_->OuterViewportScrollLayer();

        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(10, outer_viewport, outer_scroll);
        LayerImpl* child_scroll = child.get();
        outer_scroll->test_properties()->children[0]->test_properties()->AddChild(
            std::move(child));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->RootScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(),
            host_impl_->ViewportMainScrollLayer());
        host_impl_->ScrollEnd(EndState().get());
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        EXPECT_EQ(host_impl_->CurrentlyScrollingLayer(), child_scroll);
        host_impl_->ScrollEnd(EndState().get());
    }

    TEST_F(LayerTreeHostImplVirtualViewportTest,
        NoOverscrollWhenInnerViewportCantScroll)
    {
        InputHandlerScrollResult scroll_result;
        gfx::Size content_size = gfx::Size(100, 160);
        gfx::Size outer_viewport = gfx::Size(50, 80);
        gfx::Size inner_viewport = gfx::Size(25, 40);
        SetupVirtualViewportLayers(content_size, outer_viewport, inner_viewport);
        // Make inner viewport unscrollable.
        LayerImpl* inner_scroll = host_impl_->InnerViewportScrollLayer();
        inner_scroll->set_user_scrollable_horizontal(false);
        inner_scroll->set_user_scrollable_vertical(false);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        // Ensure inner viewport doesn't react to scrolls (test it's unscrollable).
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), inner_scroll->CurrentScrollOffset());
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point()).get(),
                    InputHandler::TOUCHSCREEN)
                .thread);
        scroll_result = host_impl_->ScrollBy(
            UpdateState(gfx::Point(), gfx::Vector2dF(0, 100)).get());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), inner_scroll->CurrentScrollOffset());

        // When inner viewport is unscrollable, a fling gives zero overscroll.
        EXPECT_FALSE(scroll_result.did_overscroll_root);
        EXPECT_EQ(gfx::Vector2dF(), host_impl_->accumulated_root_overscroll());
    }

    class LayerTreeHostImplWithImplicitLimitsTest : public LayerTreeHostImplTest {
    public:
        void SetUp() override
        {
            LayerTreeSettings settings = DefaultSettings();
            settings.max_memory_for_prepaint_percentage = 50;
            CreateHostImpl(settings, CreateCompositorFrameSink());
        }
    };

    TEST_F(LayerTreeHostImplWithImplicitLimitsTest, ImplicitMemoryLimits)
    {
        // Set up a memory policy and percentages which could cause
        // 32-bit integer overflows.
        ManagedMemoryPolicy mem_policy(300 * 1024 * 1024); // 300MB

        // Verify implicit limits are calculated correctly with no overflows
        host_impl_->SetMemoryPolicy(mem_policy);
        EXPECT_EQ(host_impl_->global_tile_state().hard_memory_limit_in_bytes,
            300u * 1024u * 1024u);
        EXPECT_EQ(host_impl_->global_tile_state().soft_memory_limit_in_bytes,
            150u * 1024u * 1024u);
    }

    TEST_F(LayerTreeHostImplTest, ExternalTransformReflectedInNextDraw)
    {
        const gfx::Size layer_size(100, 100);
        gfx::Transform external_transform;
        const gfx::Rect external_viewport(layer_size);
        const bool resourceless_software_draw = false;
        LayerImpl* layer = SetupScrollAndContentsLayers(layer_size);
        layer->SetDrawsContent(true);

        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);
        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        EXPECT_TRANSFORMATION_MATRIX_EQ(
            external_transform, layer->draw_properties().target_space_transform);

        external_transform.Translate(20, 20);
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);
        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        EXPECT_TRANSFORMATION_MATRIX_EQ(
            external_transform, layer->draw_properties().target_space_transform);
    }

    TEST_F(LayerTreeHostImplTest, ExternalTransformSetNeedsRedraw)
    {
        SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        const gfx::Size viewport_size(100, 100);
        host_impl_->SetViewportSize(viewport_size);

        const gfx::Transform transform_for_tile_priority;
        const gfx::Transform draw_transform;
        const gfx::Rect viewport_for_tile_priority1(viewport_size);
        const gfx::Rect viewport_for_tile_priority2(50, 50);
        const gfx::Rect draw_viewport(viewport_size);
        bool resourceless_software_draw = false;

        // Clear any damage.
        host_impl_->SetExternalTilePriorityConstraints(viewport_for_tile_priority1,
            transform_for_tile_priority);
        host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw);
        last_on_draw_frame_.reset();

        // Setting new constraints needs redraw.
        did_request_redraw_ = false;
        host_impl_->SetExternalTilePriorityConstraints(viewport_for_tile_priority2,
            transform_for_tile_priority);
        EXPECT_TRUE(did_request_redraw_);
        host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw);
        EXPECT_FALSE(last_on_draw_frame_->has_no_damage);
    }

    TEST_F(LayerTreeHostImplTest, OnDrawConstraintSetNeedsRedraw)
    {
        SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        const gfx::Size viewport_size(100, 100);
        host_impl_->SetViewportSize(viewport_size);

        const gfx::Transform draw_transform;
        const gfx::Rect draw_viewport1(viewport_size);
        const gfx::Rect draw_viewport2(50, 50);
        bool resourceless_software_draw = false;

        // Clear any damage.
        host_impl_->OnDraw(draw_transform, draw_viewport1,
            resourceless_software_draw);
        last_on_draw_frame_.reset();

        // Same draw params does not swap.
        did_request_redraw_ = false;
        host_impl_->OnDraw(draw_transform, draw_viewport1,
            resourceless_software_draw);
        EXPECT_FALSE(did_request_redraw_);
        EXPECT_TRUE(last_on_draw_frame_->has_no_damage);
        last_on_draw_frame_.reset();

        // Different draw params does swap.
        did_request_redraw_ = false;
        host_impl_->OnDraw(draw_transform, draw_viewport2,
            resourceless_software_draw);
        EXPECT_TRUE(did_request_redraw_);
        EXPECT_FALSE(last_on_draw_frame_->has_no_damage);
    }

    // We will force the touch event handler to be passive if we touch on a
    // layer which is the current scrolling layer or its descendant.
    TEST_F(LayerTreeHostImplTest, TouchInsideOrOutsideFlingLayer)
    {
        gfx::Size surface_size(100, 100);
        gfx::Size inner_size(50, 50);

        LayerImpl* root = CreateBasicVirtualViewportLayers(surface_size, surface_size);
        root->test_properties()->force_render_surface = true;

        std::unique_ptr<LayerImpl> child = CreateScrollableLayer(26, surface_size, root);
        LayerImpl* child_layer = child.get();

        std::unique_ptr<LayerImpl> grand_child = CreateScrollableLayer(27, inner_size, root);
        LayerImpl* grand_child_layer = grand_child.get();
        child->test_properties()->AddChild(std::move(grand_child));
        root->test_properties()->AddChild(std::move(child));

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();
        DrawFrame();

        {
            // Touch on a layer which does not have a handler will return kNone.
            EXPECT_EQ(InputHandler::TouchStartEventListenerType::NO_HANDLER,
                host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(10, 10)));
            child_layer->SetTouchEventHandlerRegion(gfx::Rect(0, 0, 100, 100));
            grand_child_layer->SetTouchEventHandlerRegion(gfx::Rect(0, 0, 100, 100));
            EXPECT_EQ(InputHandler::TouchStartEventListenerType::HANDLER,
                host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(10, 10)));
            // Flinging the child layer.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(60, 60)).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_EQ(child_layer, host_impl_->CurrentlyScrollingLayer());
            // Touch on the child layer, which is an active fling layer, the touch
            // event handler will force to be passive.
            EXPECT_EQ(
                InputHandler::TouchStartEventListenerType::HANDLER_ON_SCROLLING_LAYER,
                host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(70, 80)));
            // Touch on the grand child layer, which is a descendant of an active fling
            // layer, the touch event handler will force to be passive.
            EXPECT_EQ(
                InputHandler::TouchStartEventListenerType::HANDLER_ON_SCROLLING_LAYER,
                host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(20, 30)));

            // Now flinging on the grand child layer.
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point(10, 10)).get(),
                        InputHandler::TOUCHSCREEN)
                    .thread);
            EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_->FlingScrollBegin().thread);
            EXPECT_EQ(grand_child_layer, host_impl_->CurrentlyScrollingLayer());
            // Touch on the child layer, the touch event handler will still be blocking.
            EXPECT_EQ(InputHandler::TouchStartEventListenerType::HANDLER,
                host_impl_->EventListenerTypeForTouchStartAt(gfx::Point(60, 60)));
        }
    }

    class ResourcelessSoftwareLayerTreeHostImplTest : public LayerTreeHostImplTest {
    protected:
        std::unique_ptr<CompositorFrameSink> CreateCompositorFrameSink() override
        {
            return FakeCompositorFrameSink::Create3d();
        }
    };

    TEST_F(ResourcelessSoftwareLayerTreeHostImplTest,
        ResourcelessSoftwareSetNeedsRedraw)
    {
        SetupRootLayerImpl(LayerImpl::Create(host_impl_->active_tree(), 1));
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        const gfx::Size viewport_size(100, 100);
        host_impl_->SetViewportSize(viewport_size);

        const gfx::Transform draw_transform;
        const gfx::Rect draw_viewport(viewport_size);
        bool resourceless_software_draw = false;

        // Clear any damage.
        host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw);
        last_on_draw_frame_.reset();

        // Always swap even if same draw params.
        resourceless_software_draw = true;
        host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw);
        EXPECT_FALSE(last_on_draw_frame_->has_no_damage);
        last_on_draw_frame_.reset();

        // Next hardware draw has damage.
        resourceless_software_draw = false;
        host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw);
        EXPECT_FALSE(last_on_draw_frame_->has_no_damage);
    }

    TEST_F(ResourcelessSoftwareLayerTreeHostImplTest,
        ResourcelessSoftwareDrawSkipsUpdateTiles)
    {
        const gfx::Size viewport_size(100, 100);
        host_impl_->SetViewportSize(viewport_size);

        host_impl_->CreatePendingTree();
        scoped_refptr<FakeRasterSource> raster_source(
            FakeRasterSource::CreateFilled(viewport_size));
        std::unique_ptr<FakePictureLayerImpl> layer(
            FakePictureLayerImpl::CreateWithRasterSource(host_impl_->pending_tree(),
                11, raster_source));
        layer->SetBounds(viewport_size);
        layer->SetDrawsContent(true);
        host_impl_->pending_tree()->SetRootLayerForTesting(std::move(layer));

        host_impl_->pending_tree()->BuildPropertyTreesForTesting();
        host_impl_->ActivateSyncTree();

        const gfx::Transform draw_transform;
        const gfx::Rect draw_viewport(viewport_size);
        bool resourceless_software_draw = false;

        // Regular draw causes UpdateTiles.
        did_request_prepare_tiles_ = false;
        host_impl_->OnDraw(draw_transform, draw_viewport, resourceless_software_draw);
        EXPECT_TRUE(did_request_prepare_tiles_);
        host_impl_->PrepareTiles();

        // Resourceless draw skips UpdateTiles.
        const gfx::Rect new_draw_viewport(50, 50);
        resourceless_software_draw = true;
        did_request_prepare_tiles_ = false;
        host_impl_->OnDraw(draw_transform, new_draw_viewport,
            resourceless_software_draw);
        EXPECT_FALSE(did_request_prepare_tiles_);
    }

    TEST_F(LayerTreeHostImplTest, ExternalTileConstraintReflectedInPendingTree)
    {
        EXPECT_FALSE(host_impl_->CommitToActiveTree());
        const gfx::Size layer_size(100, 100);
        host_impl_->SetViewportSize(layer_size);
        bool update_lcd_text = false;

        // Set up active and pending tree.
        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->SetRootLayerForTesting(
            LayerImpl::Create(host_impl_->pending_tree(), 1));
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();
        host_impl_->pending_tree()->UpdateDrawProperties(update_lcd_text);

        host_impl_->ActivateSyncTree();
        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->UpdateDrawProperties(update_lcd_text);

        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->UpdateDrawProperties(update_lcd_text);
        host_impl_->active_tree()->UpdateDrawProperties(update_lcd_text);

        EXPECT_FALSE(host_impl_->pending_tree()->needs_update_draw_properties());
        EXPECT_FALSE(host_impl_->active_tree()->needs_update_draw_properties());

        // Update external constraints should set_needs_update_draw_properties on
        // both trees.
        gfx::Transform external_transform;
        gfx::Rect external_viewport(10, 20);
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);
        EXPECT_TRUE(host_impl_->pending_tree()->needs_update_draw_properties());
        EXPECT_TRUE(host_impl_->active_tree()->needs_update_draw_properties());
    }

    TEST_F(LayerTreeHostImplTest, ExternalViewportAffectsVisibleRects)
    {
        const gfx::Size layer_size(100, 100);
        SetupScrollAndContentsLayers(layer_size);
        LayerImpl* content_layer = host_impl_->active_tree()
                                       ->OuterViewportScrollLayer()
                                       ->test_properties()
                                       ->children[0];

        bool update_lcd_text = false;

        host_impl_->SetViewportSize(gfx::Size(90, 90));
        host_impl_->active_tree()->UpdateDrawProperties(update_lcd_text);
        EXPECT_EQ(gfx::Rect(90, 90), content_layer->visible_layer_rect());

        gfx::Transform external_transform;
        gfx::Rect external_viewport(10, 20);
        bool resourceless_software_draw = false;
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);
        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        EXPECT_EQ(gfx::Rect(10, 20), content_layer->visible_layer_rect());

        // Clear the external viewport.
        external_viewport = gfx::Rect();
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        EXPECT_EQ(gfx::Rect(90, 90), content_layer->visible_layer_rect());
    }

    TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsVisibleRects)
    {
        const gfx::Size layer_size(100, 100);
        SetupScrollAndContentsLayers(layer_size);
        LayerImpl* content_layer = host_impl_->active_tree()
                                       ->OuterViewportScrollLayer()
                                       ->test_properties()
                                       ->children[0];

        bool update_lcd_text = false;

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        host_impl_->active_tree()->UpdateDrawProperties(update_lcd_text);
        EXPECT_EQ(gfx::Rect(50, 50), content_layer->visible_layer_rect());

        gfx::Transform external_transform;
        external_transform.Translate(10, 10);
        external_transform.Scale(2, 2);
        gfx::Rect external_viewport;
        bool resourceless_software_draw = false;
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        // Visible rects should now be shifted and scaled because of the external
        // transform.
        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        EXPECT_EQ(gfx::Rect(20, 20), content_layer->visible_layer_rect());

        // Clear the external transform.
        external_transform = gfx::Transform();
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        EXPECT_EQ(gfx::Rect(50, 50), content_layer->visible_layer_rect());
    }

    TEST_F(LayerTreeHostImplTest, ExternalTransformAffectsSublayerScaleFactor)
    {
        const gfx::Size layer_size(100, 100);
        SetupScrollAndContentsLayers(layer_size);
        LayerImpl* content_layer = host_impl_->active_tree()
                                       ->OuterViewportScrollLayer()
                                       ->test_properties()
                                       ->children[0];
        content_layer->test_properties()->AddChild(
            LayerImpl::Create(host_impl_->active_tree(), 100));
        LayerImpl* test_layer = host_impl_->active_tree()->LayerById(100);
        test_layer->test_properties()->force_render_surface = true;
        test_layer->SetDrawsContent(true);
        test_layer->SetBounds(layer_size);
        gfx::Transform perspective_transform;
        perspective_transform.ApplyPerspectiveDepth(2);
        test_layer->test_properties()->transform = perspective_transform;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        bool update_lcd_text = false;

        host_impl_->SetViewportSize(gfx::Size(50, 50));
        host_impl_->active_tree()->UpdateDrawProperties(update_lcd_text);
        EffectNode* node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
            test_layer->effect_tree_index());
        EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(1.f, 1.f));

        gfx::Transform external_transform;
        external_transform.Translate(10, 10);
        external_transform.Scale(2, 2);
        gfx::Rect external_viewport;
        bool resourceless_software_draw = false;
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        // Transform node's sublayer scale should include the device transform scale.
        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
            test_layer->effect_tree_index());
        EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(2.f, 2.f));

        // Clear the external transform.
        external_transform = gfx::Transform();
        host_impl_->SetExternalTilePriorityConstraints(external_viewport,
            external_transform);

        host_impl_->OnDraw(external_transform, external_viewport,
            resourceless_software_draw);
        node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
            test_layer->effect_tree_index());
        EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(1.f, 1.f));
    }

    TEST_F(LayerTreeHostImplTest, ScrollAnimated)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(50, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);

        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();
        EXPECT_EQ(host_impl_->OuterViewportScrollLayer(), scrolling_layer);

        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        float y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 1 && y < 49);

        // Update target.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(200);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 50 && y < 100);
        EXPECT_EQ(scrolling_layer, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(250);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_VECTOR_EQ(gfx::ScrollOffset(0, 100),
            scrolling_layer->CurrentScrollOffset());
        EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();
    }

    TEST_F(LayerTreeHostImplTest, SecondScrollAnimatedBeginNotIgnored)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(50, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimatedBegin(gfx::Point()).thread);

        // The second ScrollAnimatedBegin should not get ignored.
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimatedBegin(gfx::Point()).thread);
    }

    // Verfify that a smooth scroll animation doesn't jump when UpdateTarget gets
    // called before the animation is started.
    TEST_F(LayerTreeHostImplTest, AnimatedScrollUpdateTargetBeforeStarting)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(50, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(200);

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->UpdateAnimationState(true);
        host_impl_->DidFinishImplFrame();

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
        // This will call ScrollOffsetAnimationCurve::UpdateTarget while the animation
        // created above is in state ANIMATION::WAITING_FOR_TARGET_AVAILABILITY and
        // doesn't have a start time.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 100)).thread);

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(250);
        begin_frame_args.sequence_number++;
        // This is when the animation above gets promoted to STARTING.
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->UpdateAnimationState(true);
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(300);
        begin_frame_args.sequence_number++;
        // This is when the animation above gets ticked.
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->UpdateAnimationState(true);
        host_impl_->DidFinishImplFrame();

        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();
        EXPECT_EQ(host_impl_->OuterViewportScrollLayer(), scrolling_layer);

        // Verify no jump.
        float y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 1 && y < 49);
    }

    TEST_F(LayerTreeHostImplTest, ScrollAnimatedWithDelay)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(50, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        // Create animation with a 100ms delay.
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 100),
                    base::TimeDelta::FromMilliseconds(100))
                .thread);
        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();
        EXPECT_EQ(host_impl_->OuterViewportScrollLayer(), scrolling_layer);

        // First tick, animation is started.
        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset());
        host_impl_->DidFinishImplFrame();

        // Second tick after 50ms, animation should be half way done since
        // the duration due to delay is 100ms.
        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(50, scrolling_layer->CurrentScrollOffset().y());
        host_impl_->DidFinishImplFrame();

        // Update target.
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 100),
                    base::TimeDelta::FromMilliseconds(150))
                .thread);

        // Third tick after 100ms, should be at the target position since update
        // target was called with a large value of jank.
        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(100);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->UpdateAnimationState(true);
        EXPECT_LT(100, scrolling_layer->CurrentScrollOffset().y());
    }

    // Test that a smooth scroll offset animation is aborted when followed by a
    // non-smooth scroll offset animation.
    TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedAborted)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        // Perform animated scroll.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);

        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();

        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_TRUE(GetImplAnimationHost()->HasAnyAnimationTargetingProperty(
            scrolling_layer->element_id(), TargetProperty::SCROLL_OFFSET));

        EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        float y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 1 && y < 49);

        // Perform instant scroll.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_
                ->ScrollBegin(BeginState(gfx::Point(0, y)).get(), InputHandler::WHEEL)
                .thread);
        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(0, y),
            InputHandler::WHEEL));
        host_impl_->ScrollBy(
            UpdateState(gfx::Point(0, y), gfx::Vector2d(0, 50)).get());
        EXPECT_TRUE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(0, y + 50),
            InputHandler::WHEEL));
        std::unique_ptr<ScrollState> scroll_state_end = EndState();
        host_impl_->ScrollEnd(scroll_state_end.get());
        EXPECT_FALSE(host_impl_->IsCurrentlyScrollingLayerAt(gfx::Point(),
            InputHandler::WHEEL));

        // The instant scroll should have marked the smooth scroll animation as
        // aborted.
        EXPECT_FALSE(GetImplAnimationHost()->HasTickingAnimationForTesting(
            scrolling_layer->element_id()));

        EXPECT_VECTOR2DF_EQ(gfx::ScrollOffset(0, y + 50),
            scrolling_layer->CurrentScrollOffset());
        EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();
    }

    // Test that a smooth scroll offset animation is marked finished when aborted
    // with the needs_completion flag. The animation is then finished on the
    // main thread.
    TEST_F(LayerTreeHostImplTimelinesTest,
        ScrollAnimatedFinishedByMainThreadScrollingReason)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        // Perform animated scroll.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 100)).thread);

        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();

        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_TRUE(GetImplAnimationHost()->HasAnyAnimationTargetingProperty(
            scrolling_layer->element_id(), TargetProperty::SCROLL_OFFSET));

        EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        float y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 1 && y < 49);

        // Abort animation.
        GetImplAnimationHost()->ScrollAnimationAbort(true /*needs_completion*/);
        host_impl_->UpdateAnimationState(true);

        // Aborting with the needs completion param should have marked the smooth
        // scroll animation as finished.
        EXPECT_FALSE(GetImplAnimationHost()->HasTickingAnimationForTesting(
            scrolling_layer->element_id()));
        EXPECT_TRUE(y > 1 && y < 49);
        EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();
    }

    // Evolved from LayerTreeHostImplTest.ScrollAnimated.
    TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimated)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);

        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();
        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        float y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 1 && y < 49);

        // Update target.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(0, 50)).thread);
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(200);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 50 && y < 100);
        EXPECT_EQ(scrolling_layer, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(250);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_VECTOR_EQ(gfx::ScrollOffset(0, 100),
            scrolling_layer->CurrentScrollOffset());
        EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();
    }

    // Test that the scroll delta for an animated scroll is distributed correctly
    // between the inner and outer viewport.
    TEST_F(LayerTreeHostImplTimelinesTest, ImplPinchZoomScrollAnimated)
    {
        const gfx::Size content_size(200, 200);
        const gfx::Size viewport_size(100, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();

        // Zoom into the page by a 2X factor
        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 2.f;
        host_impl_->active_tree()->PushPageScaleFromMainThread(
            page_scale_factor, min_page_scale, max_page_scale);
        host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);

        // Scroll by a small amount, there should be no bubbling to the outer
        // viewport.
        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(250);
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
        EXPECT_EQ(InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(10.f, 20.f))
                .thread);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(inner_scroll_layer, host_impl_->CurrentlyScrollingLayer());

        begin_frame_args.sequence_number++;
        BeginImplFrameAndAnimate(begin_frame_args, start_time);
        EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 10),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), outer_scroll_layer->CurrentScrollOffset());

        // Scroll by the inner viewport's max scroll extent, the remainder
        // should bubble up to the outer viewport.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(100.f, 100.f))
                .thread);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(inner_scroll_layer, host_impl_->CurrentlyScrollingLayer());

        begin_frame_args.sequence_number++;
        BeginImplFrameAndAnimate(begin_frame_args,
            start_time + base::TimeDelta::FromMilliseconds(350));
        EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(5, 10),
            outer_scroll_layer->CurrentScrollOffset());

        // Scroll by the outer viewport's max scroll extent, it should all go to the
        // outer viewport.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(190.f, 180.f))
                .thread);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(outer_scroll_layer, host_impl_->CurrentlyScrollingLayer());

        begin_frame_args.sequence_number++;
        BeginImplFrameAndAnimate(begin_frame_args,
            start_time + base::TimeDelta::FromMilliseconds(850));
        EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(100, 100),
            outer_scroll_layer->CurrentScrollOffset());

        // Scroll upwards by the max scroll extent. The inner viewport should animate
        // and the remainder should bubble to the outer viewport.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(-110.f, -120.f))
                .thread);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(inner_scroll_layer, host_impl_->CurrentlyScrollingLayer());

        begin_frame_args.sequence_number++;
        BeginImplFrameAndAnimate(
            begin_frame_args, start_time + base::TimeDelta::FromMilliseconds(1200));
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(95, 90),
            outer_scroll_layer->CurrentScrollOffset());
    }

    // Test that the correct viewport scroll layer is updated when the target offset
    // is updated.
    TEST_F(LayerTreeHostImplTimelinesTest, ImplPinchZoomScrollAnimatedUpdate)
    {
        const gfx::Size content_size(200, 200);
        const gfx::Size viewport_size(100, 100);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        LayerImpl* outer_scroll_layer = host_impl_->OuterViewportScrollLayer();
        LayerImpl* inner_scroll_layer = host_impl_->InnerViewportScrollLayer();

        // Zoom into the page by a 2X factor
        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 2.f;
        host_impl_->active_tree()->PushPageScaleFromMainThread(
            page_scale_factor, min_page_scale, max_page_scale);
        host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);

        // Scroll the inner viewport.
        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(50);
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(90, 90)).thread);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(inner_scroll_layer, host_impl_->CurrentlyScrollingLayer());

        begin_frame_args.sequence_number++;
        BeginImplFrameAndAnimate(begin_frame_args, start_time);
        float inner_x = inner_scroll_layer->CurrentScrollOffset().x();
        float inner_y = inner_scroll_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(inner_x > 0 && inner_x < 45);
        EXPECT_TRUE(inner_y > 0 && inner_y < 45);
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());

        // Update target.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(50, 50)).thread);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        EXPECT_EQ(inner_scroll_layer, host_impl_->CurrentlyScrollingLayer());

        // Verify that all the delta is applied to the inner viewport and nothing is
        // carried forward.
        begin_frame_args.sequence_number++;
        BeginImplFrameAndAnimate(begin_frame_args,
            start_time + base::TimeDelta::FromMilliseconds(350));
        EXPECT_VECTOR_EQ(gfx::Vector2dF(50, 50),
            inner_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 0),
            outer_scroll_layer->CurrentScrollOffset());
        EXPECT_VECTOR_EQ(gfx::Vector2dF(), outer_scroll_layer->CurrentScrollOffset());
    }

    // Test that smooth scroll offset animation doesn't happen for non user
    // scrollable layers.
    TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedNotUserScrollable)
    {
        const gfx::Size content_size(1000, 1000);
        const gfx::Size viewport_size(500, 500);
        CreateBasicVirtualViewportLayers(viewport_size, content_size);

        host_impl_->OuterViewportScrollLayer()->set_user_scrollable_vertical(true);
        host_impl_->OuterViewportScrollLayer()->set_user_scrollable_horizontal(false);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);

        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(50, 50)).thread);

        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();
        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_EQ(gfx::ScrollOffset(), scrolling_layer->CurrentScrollOffset());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(50);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        // Should not have scrolled horizontally.
        EXPECT_EQ(0, scrolling_layer->CurrentScrollOffset().x());
        float y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 1 && y < 49);

        // Update target.
        EXPECT_EQ(
            InputHandler::SCROLL_ON_IMPL_THREAD,
            host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(50, 50)).thread);
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(200);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        y = scrolling_layer->CurrentScrollOffset().y();
        EXPECT_TRUE(y > 50 && y < 100);
        EXPECT_EQ(scrolling_layer, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(250);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);

        EXPECT_VECTOR_EQ(gfx::ScrollOffset(0, 100),
            scrolling_layer->CurrentScrollOffset());
        EXPECT_EQ(NULL, host_impl_->CurrentlyScrollingLayer());
        host_impl_->DidFinishImplFrame();
    }

    // Test that smooth scrolls clamp correctly when bounds change mid-animation.
    TEST_F(LayerTreeHostImplTimelinesTest, ScrollAnimatedChangingBounds)
    {
        const gfx::Size old_content_size(1000, 1000);
        const gfx::Size new_content_size(750, 750);
        const gfx::Size viewport_size(500, 500);

        LayerImpl* content_layer = CreateBasicVirtualViewportLayers(viewport_size, old_content_size);

        DrawFrame();

        base::TimeTicks start_time = base::TimeTicks() + base::TimeDelta::FromMilliseconds(100);
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 1);

        host_impl_->ScrollAnimated(gfx::Point(), gfx::Vector2d(500, 500));
        LayerImpl* scrolling_layer = host_impl_->CurrentlyScrollingLayer();

        begin_frame_args.frame_time = start_time;
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        host_impl_->DidFinishImplFrame();

        content_layer->SetBounds(new_content_size);
        scrolling_layer->SetBounds(new_content_size);
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        begin_frame_args.frame_time = start_time + base::TimeDelta::FromMilliseconds(200);
        begin_frame_args.sequence_number++;
        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->Animate();
        host_impl_->UpdateAnimationState(true);
        host_impl_->DidFinishImplFrame();

        EXPECT_EQ(gfx::ScrollOffset(250, 250),
            scrolling_layer->CurrentScrollOffset());
    }

    TEST_F(LayerTreeHostImplTest, InvalidLayerNotAddedToRasterQueue)
    {
        host_impl_->CreatePendingTree();

        Region empty_invalidation;
        scoped_refptr<RasterSource> raster_source_with_tiles(
            FakeRasterSource::CreateFilled(gfx::Size(10, 10)));

        std::unique_ptr<FakePictureLayerImpl> layer = FakePictureLayerImpl::Create(host_impl_->pending_tree(), 11);
        layer->SetBounds(gfx::Size(10, 10));
        layer->set_gpu_raster_max_texture_size(host_impl_->device_viewport_size());
        layer->SetDrawsContent(true);
        layer->tilings()->AddTiling(1.0f, raster_source_with_tiles);
        layer->UpdateRasterSource(raster_source_with_tiles, &empty_invalidation,
            nullptr);
        layer->tilings()->tiling_at(0)->set_resolution(
            TileResolution::HIGH_RESOLUTION);
        layer->tilings()->tiling_at(0)->CreateAllTilesForTesting();
        layer->tilings()->UpdateTilePriorities(gfx::Rect(gfx::Size(10, 10)), 1.f, 1.0,
            Occlusion(), true);
        host_impl_->pending_tree()->SetRootLayerForTesting(std::move(layer));

        FakePictureLayerImpl* root_layer = static_cast<FakePictureLayerImpl*>(
            host_impl_->pending_tree()->root_layer_for_testing());

        root_layer->set_has_valid_tile_priorities(true);
        std::unique_ptr<RasterTilePriorityQueue> non_empty_raster_priority_queue_all = host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES,
            RasterTilePriorityQueue::Type::ALL);
        EXPECT_FALSE(non_empty_raster_priority_queue_all->IsEmpty());

        root_layer->set_has_valid_tile_priorities(false);
        std::unique_ptr<RasterTilePriorityQueue> empty_raster_priority_queue_all = host_impl_->BuildRasterQueue(TreePriority::SAME_PRIORITY_FOR_BOTH_TREES,
            RasterTilePriorityQueue::Type::ALL);
        EXPECT_TRUE(empty_raster_priority_queue_all->IsEmpty());
    }

    TEST_F(LayerTreeHostImplTest, DidBecomeActive)
    {
        host_impl_->CreatePendingTree();
        host_impl_->ActivateSyncTree();
        host_impl_->CreatePendingTree();

        LayerTreeImpl* pending_tree = host_impl_->pending_tree();

        std::unique_ptr<FakePictureLayerImpl> pending_layer = FakePictureLayerImpl::Create(pending_tree, 10);
        FakePictureLayerImpl* raw_pending_layer = pending_layer.get();
        pending_tree->SetRootLayerForTesting(std::move(pending_layer));
        ASSERT_EQ(raw_pending_layer, pending_tree->root_layer_for_testing());

        EXPECT_EQ(0u, raw_pending_layer->did_become_active_call_count());
        pending_tree->DidBecomeActive();
        EXPECT_EQ(1u, raw_pending_layer->did_become_active_call_count());

        std::unique_ptr<FakePictureLayerImpl> mask_layer = FakePictureLayerImpl::Create(pending_tree, 11);
        FakePictureLayerImpl* raw_mask_layer = mask_layer.get();
        raw_pending_layer->test_properties()->SetMaskLayer(std::move(mask_layer));
        ASSERT_EQ(raw_mask_layer, raw_pending_layer->test_properties()->mask_layer);
        pending_tree->BuildPropertyTreesForTesting();

        EXPECT_EQ(1u, raw_pending_layer->did_become_active_call_count());
        EXPECT_EQ(0u, raw_mask_layer->did_become_active_call_count());
        pending_tree->DidBecomeActive();
        EXPECT_EQ(2u, raw_pending_layer->did_become_active_call_count());
        EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count());

        pending_tree->BuildPropertyTreesForTesting();

        EXPECT_EQ(2u, raw_pending_layer->did_become_active_call_count());
        EXPECT_EQ(1u, raw_mask_layer->did_become_active_call_count());
        pending_tree->DidBecomeActive();
        EXPECT_EQ(3u, raw_pending_layer->did_become_active_call_count());
        EXPECT_EQ(2u, raw_mask_layer->did_become_active_call_count());
    }

    TEST_F(LayerTreeHostImplTest, WheelScrollWithPageScaleFactorOnInnerLayer)
    {
        LayerImpl* scroll_layer = SetupScrollAndContentsLayers(gfx::Size(100, 100));
        host_impl_->SetViewportSize(gfx::Size(50, 50));
        DrawFrame();

        EXPECT_EQ(scroll_layer, host_impl_->InnerViewportScrollLayer());

        float min_page_scale = 1.f, max_page_scale = 4.f;
        float page_scale_factor = 1.f;

        // The scroll deltas should have the page scale factor applied.
        {
            host_impl_->active_tree()->PushPageScaleFromMainThread(
                page_scale_factor, min_page_scale, max_page_scale);
            host_impl_->active_tree()->SetPageScaleOnActiveTree(page_scale_factor);
            SetScrollOffsetDelta(scroll_layer, gfx::Vector2d());

            float page_scale_delta = 2.f;
            host_impl_->ScrollBegin(BeginState(gfx::Point()).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->PinchGestureBegin();
            host_impl_->PinchGestureUpdate(page_scale_delta, gfx::Point());
            host_impl_->PinchGestureEnd();
            host_impl_->ScrollEnd(EndState().get());

            gfx::Vector2dF scroll_delta(0, 5);
            EXPECT_EQ(
                InputHandler::SCROLL_ON_IMPL_THREAD,
                host_impl_
                    ->ScrollBegin(BeginState(gfx::Point()).get(), InputHandler::WHEEL)
                    .thread);
            EXPECT_VECTOR_EQ(gfx::Vector2dF(), scroll_layer->CurrentScrollOffset());

            host_impl_->ScrollBy(UpdateState(gfx::Point(), scroll_delta).get());
            host_impl_->ScrollEnd(EndState().get());
            EXPECT_VECTOR_EQ(gfx::Vector2dF(0, 2.5),
                scroll_layer->CurrentScrollOffset());
        }
    }

    class LayerTreeHostImplCountingLostSurfaces : public LayerTreeHostImplTest {
    public:
        LayerTreeHostImplCountingLostSurfaces()
            : num_lost_surfaces_(0)
        {
        }
        void DidLoseCompositorFrameSinkOnImplThread() override
        {
            num_lost_surfaces_++;
        }

    protected:
        int num_lost_surfaces_;
    };

    TEST_F(LayerTreeHostImplCountingLostSurfaces, TwiceLostSurface)
    {
        // Really we just need at least one client notification each time
        // we go from having a valid output surface to not having a valid output
        // surface.
        EXPECT_EQ(0, num_lost_surfaces_);
        host_impl_->DidLoseCompositorFrameSink();
        EXPECT_EQ(1, num_lost_surfaces_);
        host_impl_->DidLoseCompositorFrameSink();
        EXPECT_LE(1, num_lost_surfaces_);
    }

    size_t CountRenderPassesWithId(const RenderPassList& list, int id)
    {
        return std::count_if(
            list.begin(), list.end(),
            [id](const std::unique_ptr<RenderPass>& p) { return p->id == id; });
    }

    TEST_F(LayerTreeHostImplTest, RemoveUnreferencedRenderPass)
    {
        LayerTreeHostImpl::FrameData frame;
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass3 = frame.render_passes.back().get();
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass2 = frame.render_passes.back().get();
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass1 = frame.render_passes.back().get();

        pass1->SetNew(1, gfx::Rect(), gfx::Rect(), gfx::Transform());
        pass2->SetNew(2, gfx::Rect(), gfx::Rect(), gfx::Transform());
        pass3->SetNew(3, gfx::Rect(), gfx::Rect(), gfx::Transform());

        // Add a quad to each pass so they aren't empty.
        SolidColorDrawQuad* color_quad;
        color_quad = pass1->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
        color_quad->material = DrawQuad::SOLID_COLOR;
        color_quad = pass2->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
        color_quad->material = DrawQuad::SOLID_COLOR;
        color_quad = pass3->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
        color_quad->material = DrawQuad::SOLID_COLOR;

        // pass3 is referenced by pass2.
        RenderPassDrawQuad* rpdq = pass2->CreateAndAppendDrawQuad<RenderPassDrawQuad>();
        rpdq->material = DrawQuad::RENDER_PASS;
        rpdq->render_pass_id = pass3->id;

        // But pass2 is not referenced by pass1. So pass2 and pass3 should be culled.
        FakeLayerTreeHostImpl::RemoveRenderPasses(&frame);
        EXPECT_EQ(1u, frame.render_passes.size());
        EXPECT_EQ(1u, CountRenderPassesWithId(frame.render_passes, 1));
        EXPECT_EQ(0u, CountRenderPassesWithId(frame.render_passes, 2));
        EXPECT_EQ(0u, CountRenderPassesWithId(frame.render_passes, 3));
        EXPECT_EQ(1, frame.render_passes[0]->id);
    }

    TEST_F(LayerTreeHostImplTest, RemoveEmptyRenderPass)
    {
        LayerTreeHostImpl::FrameData frame;
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass3 = frame.render_passes.back().get();
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass2 = frame.render_passes.back().get();
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass1 = frame.render_passes.back().get();

        pass1->SetNew(1, gfx::Rect(), gfx::Rect(), gfx::Transform());
        pass2->SetNew(2, gfx::Rect(), gfx::Rect(), gfx::Transform());
        pass3->SetNew(3, gfx::Rect(), gfx::Rect(), gfx::Transform());

        // pass1 is not empty, but pass2 and pass3 are.
        SolidColorDrawQuad* color_quad;
        color_quad = pass1->CreateAndAppendDrawQuad<SolidColorDrawQuad>();
        color_quad->material = DrawQuad::SOLID_COLOR;

        // pass3 is referenced by pass2.
        RenderPassDrawQuad* rpdq = pass2->CreateAndAppendDrawQuad<RenderPassDrawQuad>();
        rpdq->material = DrawQuad::RENDER_PASS;
        rpdq->render_pass_id = pass3->id;

        // pass2 is referenced by pass1.
        rpdq = pass1->CreateAndAppendDrawQuad<RenderPassDrawQuad>();
        rpdq->material = DrawQuad::RENDER_PASS;
        rpdq->render_pass_id = pass2->id;

        // Since pass3 is empty it should be removed. Then pass2 is empty too, and
        // should be removed.
        FakeLayerTreeHostImpl::RemoveRenderPasses(&frame);
        EXPECT_EQ(1u, frame.render_passes.size());
        EXPECT_EQ(1u, CountRenderPassesWithId(frame.render_passes, 1));
        EXPECT_EQ(0u, CountRenderPassesWithId(frame.render_passes, 2));
        EXPECT_EQ(0u, CountRenderPassesWithId(frame.render_passes, 3));
        EXPECT_EQ(1, frame.render_passes[0]->id);
        // The RenderPassDrawQuad should be removed from pass1.
        EXPECT_EQ(1u, pass1->quad_list.size());
        EXPECT_EQ(DrawQuad::SOLID_COLOR, pass1->quad_list.ElementAt(0)->material);
    }

    TEST_F(LayerTreeHostImplTest, DoNotRemoveEmptyRootRenderPass)
    {
        LayerTreeHostImpl::FrameData frame;
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass3 = frame.render_passes.back().get();
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass2 = frame.render_passes.back().get();
        frame.render_passes.push_back(RenderPass::Create());
        RenderPass* pass1 = frame.render_passes.back().get();

        pass1->SetNew(1, gfx::Rect(), gfx::Rect(), gfx::Transform());
        pass2->SetNew(2, gfx::Rect(), gfx::Rect(), gfx::Transform());
        pass3->SetNew(3, gfx::Rect(), gfx::Rect(), gfx::Transform());

        // pass3 is referenced by pass2.
        RenderPassDrawQuad* rpdq = pass2->CreateAndAppendDrawQuad<RenderPassDrawQuad>();
        rpdq->material = DrawQuad::RENDER_PASS;
        rpdq->render_pass_id = pass3->id;

        // pass2 is referenced by pass1.
        rpdq = pass1->CreateAndAppendDrawQuad<RenderPassDrawQuad>();
        rpdq->material = DrawQuad::RENDER_PASS;
        rpdq->render_pass_id = pass2->id;

        // Since pass3 is empty it should be removed. Then pass2 is empty too, and
        // should be removed. Then pass1 is empty too, but it's the root so it should
        // not be removed.
        FakeLayerTreeHostImpl::RemoveRenderPasses(&frame);
        EXPECT_EQ(1u, frame.render_passes.size());
        EXPECT_EQ(1u, CountRenderPassesWithId(frame.render_passes, 1));
        EXPECT_EQ(0u, CountRenderPassesWithId(frame.render_passes, 2));
        EXPECT_EQ(0u, CountRenderPassesWithId(frame.render_passes, 3));
        EXPECT_EQ(1, frame.render_passes[0]->id);
        // The RenderPassDrawQuad should be removed from pass1.
        EXPECT_EQ(0u, pass1->quad_list.size());
    }

    class FakeVideoFrameController : public VideoFrameController {
    public:
        void OnBeginFrame(const BeginFrameArgs& args) override
        {
            begin_frame_args_ = args;
            did_draw_frame_ = false;
        }

        void DidDrawFrame() override { did_draw_frame_ = true; }

        const BeginFrameArgs& begin_frame_args() const { return begin_frame_args_; }

        bool did_draw_frame() const { return did_draw_frame_; }

    private:
        BeginFrameArgs begin_frame_args_;
        bool did_draw_frame_ = false;
    };

    TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerInsideFrame)
    {
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2);
        FakeVideoFrameController controller;

        host_impl_->WillBeginImplFrame(begin_frame_args);
        EXPECT_FALSE(controller.begin_frame_args().IsValid());
        host_impl_->AddVideoFrameController(&controller);
        EXPECT_TRUE(controller.begin_frame_args().IsValid());
        host_impl_->DidFinishImplFrame();

        EXPECT_FALSE(controller.did_draw_frame());
        LayerTreeHostImpl::FrameData frame;
        host_impl_->DidDrawAllLayers(frame);
        EXPECT_TRUE(controller.did_draw_frame());

        controller.OnBeginFrame(begin_frame_args);
        EXPECT_FALSE(controller.did_draw_frame());
        host_impl_->RemoveVideoFrameController(&controller);
        host_impl_->DidDrawAllLayers(frame);
        EXPECT_FALSE(controller.did_draw_frame());
    }

    TEST_F(LayerTreeHostImplTest, AddVideoFrameControllerOutsideFrame)
    {
        BeginFrameArgs begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 2);
        FakeVideoFrameController controller;

        host_impl_->WillBeginImplFrame(begin_frame_args);
        host_impl_->DidFinishImplFrame();

        EXPECT_FALSE(controller.begin_frame_args().IsValid());
        host_impl_->AddVideoFrameController(&controller);
        EXPECT_FALSE(controller.begin_frame_args().IsValid());

        begin_frame_args = CreateBeginFrameArgsForTesting(BEGINFRAME_FROM_HERE, 0, 3);
        EXPECT_FALSE(controller.begin_frame_args().IsValid());
        host_impl_->WillBeginImplFrame(begin_frame_args);
        EXPECT_TRUE(controller.begin_frame_args().IsValid());

        EXPECT_FALSE(controller.did_draw_frame());
        LayerTreeHostImpl::FrameData frame;
        host_impl_->DidDrawAllLayers(frame);
        EXPECT_TRUE(controller.did_draw_frame());

        controller.OnBeginFrame(begin_frame_args);
        EXPECT_FALSE(controller.did_draw_frame());
        host_impl_->RemoveVideoFrameController(&controller);
        host_impl_->DidDrawAllLayers(frame);
        EXPECT_FALSE(controller.did_draw_frame());
    }

    // Tests that SetHasGpuRasterizationTrigger behaves as expected.
    TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusTrigger)
    {
        // Set initial state, before varying GPU rasterization trigger.
        host_impl_->SetHasGpuRasterizationTrigger(false);
        host_impl_->SetContentIsSuitableForGpuRasterization(true);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_VIEWPORT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());

        // Toggle the trigger on.
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::ON, host_impl_->gpu_rasterization_status());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());

        // And off.
        host_impl_->SetHasGpuRasterizationTrigger(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_VIEWPORT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());
    }

    // Tests that SetContentIsSuitableForGpuRasterization behaves as expected.
    TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusSuitability)
    {
        // Set initial state, before varying GPU rasterization suitability.
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());

        // Toggle suitability on.
        host_impl_->SetContentIsSuitableForGpuRasterization(true);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::ON, host_impl_->gpu_rasterization_status());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());
        EXPECT_FALSE(host_impl_->use_msaa());

        // And off.
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());
        EXPECT_FALSE(host_impl_->use_msaa());
    }

    // Tests that SetDeviceScaleFactor correctly impacts GPU rasterization.
    TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusDeviceScaleFactor)
    {
        // Create a host impl with MSAA support.
        std::unique_ptr<TestWebGraphicsContext3D> context_with_msaa = TestWebGraphicsContext3D::Create();
        context_with_msaa->SetMaxSamples(4);
        LayerTreeSettings msaaSettings = GpuRasterizationEnabledSettings();
        msaaSettings.gpu_rasterization_msaa_sample_count = -1;
        EXPECT_TRUE(CreateHostImpl(msaaSettings, FakeCompositorFrameSink::Create3d(std::move(context_with_msaa))));

        // Set initial state, before varying scale factor.
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());

        // Set device scale factor to 2, which lowers the required MSAA samples from
        // 8 to 4.
        host_impl_->active_tree()->SetDeviceScaleFactor(2.0f);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::MSAA_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());
        EXPECT_TRUE(host_impl_->use_msaa());

        // Set device scale factor back to 1.
        host_impl_->active_tree()->SetDeviceScaleFactor(1.0f);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());
        EXPECT_FALSE(host_impl_->use_msaa());
    }

    // Tests that explicit MSAA sample count correctly impacts GPU rasterization.
    TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusExplicitMSAACount)
    {
        // Create a host impl with MSAA support and a forced sample count of 4.
        std::unique_ptr<TestWebGraphicsContext3D> context_with_msaa = TestWebGraphicsContext3D::Create();
        context_with_msaa->SetMaxSamples(4);
        LayerTreeSettings msaaSettings = GpuRasterizationEnabledSettings();
        msaaSettings.gpu_rasterization_msaa_sample_count = 4;
        EXPECT_TRUE(CreateHostImpl(msaaSettings, FakeCompositorFrameSink::Create3d(std::move(context_with_msaa))));

        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::MSAA_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());
        EXPECT_TRUE(host_impl_->use_msaa());
    }

    // Tests that GPU rasterization overrides work as expected.
    TEST_F(LayerTreeHostImplTest, GpuRasterizationStatusOverrides)
    {
        // GPU rasterization explicitly disabled.
        LayerTreeSettings settings = DefaultSettings();
        settings.gpu_rasterization_enabled = false;
        EXPECT_TRUE(CreateHostImpl(settings, FakeCompositorFrameSink::Create3d()));
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->SetContentIsSuitableForGpuRasterization(true);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_DEVICE,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());

        // GPU rasterization explicitly forced.
        settings.gpu_rasterization_forced = true;
        EXPECT_TRUE(CreateHostImpl(settings, FakeCompositorFrameSink::Create3d()));

        host_impl_->SetHasGpuRasterizationTrigger(false);
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::ON_FORCED,
            host_impl_->gpu_rasterization_status());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());
    }

    class MsaaIsSlowLayerTreeHostImplTest : public LayerTreeHostImplTest {
    public:
        void CreateHostImplWithMsaaIsSlow(bool msaa_is_slow)
        {
            LayerTreeSettings settings = DefaultSettings();
            settings.gpu_rasterization_enabled = true;
            settings.gpu_rasterization_msaa_sample_count = 4;
            auto context_provider = TestContextProvider::Create();
            context_provider->UnboundTestContext3d()->SetMaxSamples(4);
            context_provider->UnboundTestContext3d()->set_msaa_is_slow(msaa_is_slow);
            auto msaa_is_normal_compositor_frame_sink = FakeCompositorFrameSink::Create3d(context_provider);
            EXPECT_TRUE(CreateHostImpl(
                settings, std::move(msaa_is_normal_compositor_frame_sink)));
        }
    };

    TEST_F(MsaaIsSlowLayerTreeHostImplTest, GpuRasterizationStatusMsaaIsSlow)
    {
        // Ensure that without the msaa_is_slow cap we raster unsuitable content with
        // msaa.
        CreateHostImplWithMsaaIsSlow(false);
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::MSAA_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());

        // Ensure that with the msaa_is_slow cap we don't raster unsuitable content
        // with msaa.
        CreateHostImplWithMsaaIsSlow(true);
        host_impl_->SetHasGpuRasterizationTrigger(true);
        host_impl_->SetContentIsSuitableForGpuRasterization(false);
        host_impl_->CommitComplete();
        EXPECT_EQ(GpuRasterizationStatus::OFF_CONTENT,
            host_impl_->gpu_rasterization_status());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());
    }

    // A mock output surface which lets us detect calls to ForceReclaimResources.
    class MockReclaimResourcesCompositorFrameSink : public FakeCompositorFrameSink {
    public:
        MockReclaimResourcesCompositorFrameSink()
            : FakeCompositorFrameSink(TestContextProvider::Create(),
                TestContextProvider::CreateWorker())
        {
        }

        MOCK_METHOD0(ForceReclaimResources, void());
    };

    // Display::Draw (and the planned Display Scheduler) currently rely on resources
    // being reclaimed to block drawing between BeginCommit / Swap. This test
    // ensures that BeginCommit triggers ForceReclaimResources. See
    // crbug.com/489515.
    TEST_F(LayerTreeHostImplTest, BeginCommitReclaimsResources)
    {
        auto compositor_frame_sink = base::MakeUnique<MockReclaimResourcesCompositorFrameSink>();
        // Hold an unowned pointer to the output surface to use for mock expectations.
        MockReclaimResourcesCompositorFrameSink* mock_compositor_frame_sink = compositor_frame_sink.get();

        CreateHostImpl(DefaultSettings(), std::move(compositor_frame_sink));
        EXPECT_CALL(*mock_compositor_frame_sink, ForceReclaimResources()).Times(1);
        host_impl_->BeginCommit();
    }

    TEST_F(LayerTreeHostImplTest, UpdatePageScaleFactorOnActiveTree)
    {
        // Check page scale factor update in property trees when an update is made
        // on the active tree.
        host_impl_->CreatePendingTree();
        host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 3.f);
        CreateScrollAndContentsLayers(host_impl_->pending_tree(),
            gfx::Size(100, 100));
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();
        host_impl_->ActivateSyncTree();
        DrawFrame();

        host_impl_->CreatePendingTree();
        host_impl_->active_tree()->SetPageScaleOnActiveTree(2.f);
        LayerImpl* page_scale_layer = host_impl_->active_tree()->PageScaleLayer();

        TransformNode* active_tree_node = host_impl_->active_tree()->property_trees()->transform_tree.Node(
            page_scale_layer->transform_tree_index());
        // SetPageScaleOnActiveTree also updates the factors in property trees.
        EXPECT_EQ(active_tree_node->post_local_scale_factor, 2.f);
        EXPECT_EQ(host_impl_->active_tree()->current_page_scale_factor(), 2.f);

        TransformNode* pending_tree_node = host_impl_->pending_tree()->property_trees()->transform_tree.Node(
            page_scale_layer->transform_tree_index());
        EXPECT_EQ(pending_tree_node->post_local_scale_factor, 1.f);
        EXPECT_EQ(host_impl_->pending_tree()->current_page_scale_factor(), 2.f);

        host_impl_->pending_tree()->UpdateDrawProperties(false);
        pending_tree_node = host_impl_->pending_tree()->property_trees()->transform_tree.Node(
            page_scale_layer->transform_tree_index());
        EXPECT_EQ(pending_tree_node->post_local_scale_factor, 2.f);

        host_impl_->ActivateSyncTree();
        host_impl_->active_tree()->UpdateDrawProperties(false);
        active_tree_node = host_impl_->active_tree()->property_trees()->transform_tree.Node(
            page_scale_layer->transform_tree_index());
        EXPECT_EQ(active_tree_node->post_local_scale_factor, 2.f);
    }

    TEST_F(LayerTreeHostImplTest, SubLayerScaleForNodeInSubtreeOfPageScaleLayer)
    {
        // Checks that the sublayer scale of a transform node in the subtree of the
        // page scale layer is updated without a property tree rebuild.
        host_impl_->active_tree()->PushPageScaleFromMainThread(1.f, 1.f, 3.f);
        CreateScrollAndContentsLayers(host_impl_->active_tree(), gfx::Size(100, 100));
        LayerImpl* page_scale_layer = host_impl_->active_tree()->PageScaleLayer();
        page_scale_layer->test_properties()->AddChild(
            LayerImpl::Create(host_impl_->active_tree(), 100));

        LayerImpl* in_subtree_of_page_scale_layer = host_impl_->active_tree()->LayerById(100);
        in_subtree_of_page_scale_layer->test_properties()->force_render_surface = true;
        host_impl_->active_tree()->BuildPropertyTreesForTesting();

        DrawFrame();

        EffectNode* node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
            in_subtree_of_page_scale_layer->effect_tree_index());
        EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(1.f, 1.f));

        host_impl_->active_tree()->SetPageScaleOnActiveTree(2.f);

        DrawFrame();

        in_subtree_of_page_scale_layer = host_impl_->active_tree()->LayerById(100);
        node = host_impl_->active_tree()->property_trees()->effect_tree.Node(
            in_subtree_of_page_scale_layer->effect_tree_index());
        EXPECT_EQ(node->surface_contents_scale, gfx::Vector2dF(2.f, 2.f));
    }

    TEST_F(LayerTreeHostImplTest, JitterTest)
    {
        host_impl_->SetViewportSize(gfx::Size(100, 100));

        host_impl_->CreatePendingTree();
        CreateScrollAndContentsLayers(host_impl_->pending_tree(),
            gfx::Size(100, 100));
        host_impl_->pending_tree()->BuildPropertyTreesForTesting();

        host_impl_->pending_tree()->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
        const int scroll = 5;
        int accumulated_scroll = 0;
        for (int i = 0; i < host_impl_->pending_tree()->kFixedPointHitsThreshold + 1;
             ++i) {
            host_impl_->ActivateSyncTree();
            host_impl_->ScrollBegin(BeginState(gfx::Point(5, 5)).get(),
                InputHandler::TOUCHSCREEN);
            host_impl_->ScrollBy(
                UpdateState(gfx::Point(), gfx::Vector2dF(0, scroll)).get());
            accumulated_scroll += scroll;
            host_impl_->ScrollEnd(EndState().get());
            host_impl_->active_tree()->UpdateDrawProperties(false);

            host_impl_->CreatePendingTree();
            host_impl_->pending_tree()->set_source_frame_number(i + 1);
            LayerImpl* content_layer = host_impl_->pending_tree()
                                           ->OuterViewportScrollLayer()
                                           ->test_properties()
                                           ->children[0];
            // The scroll done on the active tree is undone on the pending tree.
            gfx::Transform translate;
            translate.Translate(0, accumulated_scroll);
            content_layer->test_properties()->transform = translate;

            LayerTreeImpl* pending_tree = host_impl_->pending_tree();
            pending_tree->PushPageScaleFromMainThread(1.f, 1.f, 1.f);
            LayerImpl* last_scrolled_layer = pending_tree->LayerById(
                host_impl_->active_tree()->InnerViewportScrollLayer()->id());

            // When building property trees from impl side, the builder uses the scroll
            // offset of layer_impl to initialize the scroll offset in scroll tree:
            //  scroll_tree.synced_scroll_offset.PushFromMainThread(
            //                                   layer->CurrentScrollOffset()).
            // However, layer_impl does not store scroll_offset, so it is using scroll
            // tree's scroll offset to initialize itself. Usually this approach works
            // because this is a simple assignment. However if scroll_offset's pending
            // delta is not zero, the delta would be counted twice.
            // This hacking here is to restore the damaged scroll offset.
            gfx::ScrollOffset pending_base = pending_tree->property_trees()
                                                 ->scroll_tree.GetScrollOffsetBaseForTesting(
                                                     last_scrolled_layer->id());
            pending_tree->BuildPropertyTreesForTesting();
            pending_tree->property_trees()
                ->scroll_tree.UpdateScrollOffsetBaseForTesting(
                    last_scrolled_layer->id(), pending_base);
            pending_tree->LayerById(content_layer->id())->SetNeedsPushProperties();

            pending_tree->set_needs_update_draw_properties();
            pending_tree->UpdateDrawProperties(false);
            float jitter = LayerTreeHostCommon::CalculateLayerJitter(content_layer);
            // There should not be any jitter measured till we hit the fixed point hits
            // threshold.
            float expected_jitter = (i == pending_tree->kFixedPointHitsThreshold) ? 500 : 0;
            EXPECT_EQ(jitter, expected_jitter);
        }
    }

    // Checks that if we lose a GPU raster enabled CompositorFrameSink and replace
    // it
    // with a software CompositorFrameSink, LayerTreeHostImpl correctly re-computes
    // GPU
    // rasterization status.
    TEST_F(LayerTreeHostImplTest, RecomputeGpuRasterOnCompositorFrameSinkChange)
    {
        host_impl_->ReleaseCompositorFrameSink();
        host_impl_ = nullptr;

        LayerTreeSettings settings = DefaultSettings();
        settings.gpu_rasterization_forced = true;

        host_impl_ = LayerTreeHostImpl::Create(
            settings, this, &task_runner_provider_, &stats_instrumentation_,
            &task_graph_runner_,
            AnimationHost::CreateForTesting(ThreadInstance::IMPL), 0, nullptr);
        host_impl_->SetVisible(true);

        // InitializeRenderer with a gpu-raster enabled output surface.
        auto gpu_raster_compositor_frame_sink = FakeCompositorFrameSink::Create3d(TestWebGraphicsContext3D::Create());
        host_impl_->InitializeRenderer(gpu_raster_compositor_frame_sink.get());
        EXPECT_TRUE(host_impl_->use_gpu_rasterization());

        // Re-initialize with a software output surface.
        compositor_frame_sink_ = FakeCompositorFrameSink::CreateSoftware();
        host_impl_->InitializeRenderer(compositor_frame_sink_.get());
        EXPECT_FALSE(host_impl_->use_gpu_rasterization());
    }

    void LayerTreeHostImplTest::SetupMouseMoveAtTestScrollbarStates(
        bool main_thread_scrolling)
    {
        LayerTreeSettings settings = DefaultSettings();
        settings.scrollbar_fade_delay = base::TimeDelta::FromMilliseconds(500);
        settings.scrollbar_fade_duration = base::TimeDelta::FromMilliseconds(300);
        settings.scrollbar_animator = LayerTreeSettings::THINNING;

        gfx::Size viewport_size(300, 200);
        gfx::Size content_size(1000, 1000);
        gfx::Size child_layer_size(250, 150);
        gfx::Size scrollbar_size_1(gfx::Size(15, viewport_size.height()));
        gfx::Size scrollbar_size_2(gfx::Size(15, child_layer_size.height()));

        const int scrollbar_1_id = 10;
        const int scrollbar_2_id = 11;
        const int child_clip_id = 12;
        const int child_scroll_id = 13;

        CreateHostImpl(settings, CreateCompositorFrameSink());
        host_impl_->active_tree()->SetDeviceScaleFactor(1);
        host_impl_->SetViewportSize(viewport_size);
        CreateScrollAndContentsLayers(host_impl_->active_tree(), content_size);
        host_impl_->active_tree()->InnerViewportContainerLayer()->SetBounds(
            viewport_size);
        LayerImpl* root_scroll = host_impl_->active_tree()->OuterViewportScrollLayer();

        if (main_thread_scrolling) {
            root_scroll->set_main_thread_scrolling_reasons(
                MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
        }

        // scrollbar_1 on root scroll.
        std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar_1 = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
            scrollbar_1_id, VERTICAL, 5, 5, true,
            true);
        scrollbar_1->SetScrollLayerId(root_scroll->id());
        scrollbar_1->SetDrawsContent(true);
        scrollbar_1->SetBounds(scrollbar_size_1);
        scrollbar_1->SetTouchEventHandlerRegion(gfx::Rect(scrollbar_size_1));
        host_impl_->active_tree()
            ->InnerViewportContainerLayer()
            ->test_properties()
            ->AddChild(std::move(scrollbar_1));

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        DrawFrame();
        host_impl_->active_tree()->UpdateDrawProperties(false);

        ScrollbarAnimationControllerThinning* scrollbar_1_animation_controller = static_cast<ScrollbarAnimationControllerThinning*>(
            host_impl_->ScrollbarAnimationControllerForId(root_scroll->id()));
        EXPECT_TRUE(scrollbar_1_animation_controller);
        scrollbar_1_animation_controller->set_mouse_move_distance_for_test(40.f);

        // Mouse moves close to the scrollbar, goes over the scrollbar, and
        // moves back to where it was.
        host_impl_->MouseMoveAt(gfx::Point(100, 150));
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(40, 150));
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(10, 150));
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(40, 150));
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(100, 150));
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());

        // scrollbar_2 on child.
        std::unique_ptr<SolidColorScrollbarLayerImpl> scrollbar_2 = SolidColorScrollbarLayerImpl::Create(host_impl_->active_tree(),
            scrollbar_2_id, VERTICAL, 5, 5, true,
            true);
        std::unique_ptr<LayerImpl> child_clip = LayerImpl::Create(host_impl_->active_tree(), child_clip_id);
        std::unique_ptr<LayerImpl> child = LayerImpl::Create(host_impl_->active_tree(), child_scroll_id);
        child->SetPosition(gfx::PointF(50, 50));
        child->SetBounds(child_layer_size);
        child->SetDrawsContent(true);
        child->SetScrollClipLayer(child_clip_id);

        if (main_thread_scrolling) {
            child->set_main_thread_scrolling_reasons(
                MainThreadScrollingReason::kHasBackgroundAttachmentFixedObjects);
        }

        scrollbar_2->SetScrollLayerId(child_scroll_id);
        scrollbar_2->SetDrawsContent(true);
        scrollbar_2->SetBounds(scrollbar_size_2);

        child->test_properties()->AddChild(std::move(scrollbar_2));
        child_clip->test_properties()->AddChild(std::move(child));
        root_scroll->test_properties()->AddChild(std::move(child_clip));

        host_impl_->active_tree()->BuildPropertyTreesForTesting();
        host_impl_->active_tree()->DidBecomeActive();

        ScrollbarAnimationControllerThinning* scrollbar_2_animation_controller = static_cast<ScrollbarAnimationControllerThinning*>(
            host_impl_->ScrollbarAnimationControllerForId(child_scroll_id));
        EXPECT_TRUE(scrollbar_2_animation_controller);
        scrollbar_2_animation_controller->set_mouse_move_distance_for_test(40.f);

        // Mouse goes over scrollbar_2, moves close to scrollbar_2, moves close to
        // scrollbar_1, goes over scrollbar_1.
        host_impl_->MouseMoveAt(gfx::Point(60, 150));
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        EXPECT_TRUE(scrollbar_2_animation_controller->mouse_is_near_scrollbar());
        EXPECT_TRUE(scrollbar_2_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(100, 150));
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        EXPECT_TRUE(scrollbar_2_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_2_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(40, 150));
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        EXPECT_FALSE(scrollbar_2_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_2_animation_controller->mouse_is_over_scrollbar());
        host_impl_->MouseMoveAt(gfx::Point(10, 150));
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_near_scrollbar());
        EXPECT_TRUE(scrollbar_1_animation_controller->mouse_is_over_scrollbar());
        EXPECT_FALSE(scrollbar_2_animation_controller->mouse_is_near_scrollbar());
        EXPECT_FALSE(scrollbar_2_animation_controller->mouse_is_over_scrollbar());
    }

    TEST_F(LayerTreeHostImplTest,
        LayerTreeHostImplTestScrollbarStatesInMainThreadScorlling)
    {
        SetupMouseMoveAtTestScrollbarStates(true);
    }

    TEST_F(LayerTreeHostImplTest,
        LayerTreeHostImplTestScrollbarStatesInNotMainThreadScorlling)
    {
        SetupMouseMoveAtTestScrollbarStates(false);
    }

} // namespace
} // namespace cc
